全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-12 8 分钟 ✍️ juanwangdev

Redis 计数器与限流

Redis的INCR等原子命令完美支持计数器,结合过期和Lua脚本实现多种限流策略。

计数器实现

简单计数器

Bash
# 初始化计数器
SET counter:page:views 0

# 自增
INCR counter:page:views
# 返回: 1

# 自增指定值
INCRBY counter:page:views 100

# 自减
DECR counter:page:views

# 获取当前值
GET counter:page:views

计数器特点

Bash
- INCR/DECR原子操作
- 不存在时自动从0开始
- 值为整数,范围:-2^63 到 2^63-1

分布式计数器

多节点计数

Bash
# 各节点本地计数
INCR counter:node1:views

# 定时汇总
total = node1 + node2 + node3

# 或直接全局计数
INCR counter:global:views

Hash计数

Bash
# 多维度计数
HINCRBY stats:page:home views 1
HINCRBY stats:page:home unique_visitors 1
HINCRBY stats:page:home clicks 5

# 获取全部统计
HGETALL stats:page:home

时间窗口计数

每日计数

Bash
# 每日访问计数
INCR counter:daily:2024:01:01

# 设置次日过期
EXPIRE counter:daily:2024:01:01 86400 * 2

# 获取今日计数
GET counter:daily:2024:01:01

每小时计数

Bash
# 小时计数
INCR counter:hourly:2024:01:01:10

# 设置过期
EXPIRE counter:hourly:2024:01:01:10 3600 * 24

滑动时间窗口

Python
# 使用ZSet实现滑动窗口
ZADD window:user:1000 1700001000 "req1"
ZADD window:user:1000 1700002000 "req2"

# 移除过期请求
ZREMRANGEBYSCORE window:user:1000 0 {current_time - window_size}

# 统计窗口内请求数
ZCARD window:user:1000

固定窗口限流

实现原理

Python
固定时间窗口:
- 每分钟/每小时为一个窗口
- 突发流量可能绕过限制(边界问题)
- 实现简单

固定窗口实现

Python
def fixed_window_rate_limit(key, limit, window_seconds):
    current_window = int(time.time() / window_seconds)
    counter_key = f"{key}:{current_window}"

    # 获取当前窗口计数
    count = redis.incr(counter_key)

    # 首次计数设置过期
    if count == 1:
        redis.expire(counter_key, window_seconds)

    # 判断是否超限
    if count > limit:
        return False  # 限流

    return True  # 通过

固定窗口缺点

Python
边界问题:
- 11:00:59突发100请求
- 12:00:01又突发100请求
- 短时间内200请求绕过100的限制

滑动窗口限流

ZSet实现滑动窗口

lua
def sliding_window_rate_limit(key, limit, window_seconds):
    now = time.time()
    window_start = now - window_seconds

    # 移除窗口外的请求
    redis.zremrangebyscore(key, 0, window_start)

    # 统计窗口内请求数
    count = redis.zcard(key)

    if count >= limit:
        return False  # 限流

    # 添加当前请求
    redis.zadd(key, {str(now): now})

    # 设置过期时间
    redis.expire(key, window_seconds)

    return True  # 通过

滑动窗口特点

Python
- 窗口平滑滑动
- 解决固定窗口边界问题
- 精确控制时间窗口
- 内存占用较高(存储每次请求时间戳)

令牌桶限流

令牌桶原理

Python
- 以固定速率生成令牌放入桶中
- 桶有最大容量
- 请求消耗一个令牌
- 令牌不足则拒绝
- 允许突发流量(桶内令牌积累)

令牌桶实现

Python
def token_bucket_rate_limit(key, rate, capacity):
    now = time.time()

    # 获取上次更新时间和当前令牌数
    last_time = redis.hget(key, "last_time") or 0
    tokens = redis.hget(key, "tokens") or capacity

    # 计算新令牌数
    elapsed = now - float(last_time)
    new_tokens = min(capacity, float(tokens) + elapsed * rate)

    if new_tokens >= 1:
        # 消耗一个令牌
        redis.hset(key, "tokens", new_tokens - 1)
        redis.hset(key, "last_time", now)
        redis.expire(key, int(capacity / rate) + 10)
        return True

    return False  # 令牌不足,限流

漏桶限流

漏桶原理

Python
- 请求以任意速率进入桶
- 桶以固定速率漏水(处理请求)
- 桶满则拒绝新请求
- 输出流量平滑
- 不允许突发流量

漏桶实现

text
def leaky_bucket_rate_limit(key, rate, capacity):
    now = time.time()

    # 获取上次漏水时间和桶内水量
    last_time = redis.hget(key, "last_time") or now
    water = redis.hget(key, "water") or 0

    # 计算漏水量
    elapsed = now - float(last_time)
    leaked = elapsed * rate
    water = max(0, float(water) - leaked)

    # 检查桶是否溢出
    if water < capacity:
        # 加入一滴水
        redis.hset(key, "water", water + 1)
        redis.hset(key, "last_time", now)
        redis.expire(key, int(capacity / rate) + 10)
        return True

    return False  # 桶满,限流

Lua脚本限流

滑动窗口Lua脚本

text
-- 滑动窗口限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 移除过期请求
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)

-- 统计当前数量
local count = redis.call('ZCARD', key)

if count < limit then
    -- 添加请求
    redis.call('ZADD', key, now, now)
    redis.call('EXPIRE', key, window)
    return 1  -- 通过
else
    return 0  -- 限流
end

使用脚本

text
script = "
...Lua脚本内容...
"
sha = redis.script_load(script)

def rate_limit_lua(key, limit, window):
    now = int(time.time() * 1000)
    return redis.evalsha(sha, 1, key, limit, window, now)

限流算法对比

算法特点突发流量实现复杂度适用场景
固定窗口简单允许边界突发简单限流
滑动窗口精确严格控制API限流
令牌桶允许突发允许流量控制
漏桶平滑输出不允许速率限制

应用场景

API限流

text
# 用户API调用限制
def api_rate_limit(user_id):
    key = f"rate_limit:api:{user_id}"
    return sliding_window_rate_limit(key, 100, 60)  # 每分钟100次

IP限流

text
# IP请求限制
def ip_rate_limit(ip):
    key = f"rate_limit:ip:{ip}"
    return fixed_window_rate_limit(key, 1000, 60)  # 每分钟1000次

接口防护

text
# 关键接口严格限流
def sensitive_api_limit(user_id):
    key = f"rate_limit:sensitive:{user_id}"
    return sliding_window_rate_limit(key, 10, 60)  # 每分钟10次

要点总结

  • INCR/DECR原子操作实现简单计数器
  • Hash实现多维度计数,HINCRBY原子增减
  • 固定窗口:简单但有边界突发问题
  • 滑动窗口:精确控制,ZSet实现
  • 令牌桶:允许突发流量,适合流量控制
  • 漏桶:平滑输出,适合速率限制
  • Lua脚本保证限流逻辑原子性
  • 根据场景选择限流算法:API限流用滑动窗口,流量控制用令牌桶
  • 设置合理限流阈值和窗口时间

📝 发现内容有误?点击此处直接编辑

← 上一篇 Redis 缓存应用
下一篇 → Redis集群主从复制与一致性
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库