Redis 限流的核心是保存共享的配额状态,并原子地判断、更新和设置生命周期。固定窗口计数简单,滑动窗口更平滑,令牌桶能允许受控突发;没有一种方案适合所有接口。
固定窗口有什么边界?
按用户和分钟构造 Key,对它 INCR,首次计数时设置 TTL,超过阈值拒绝。若 INCR 成功但客户端在 EXPIRE 前退出,Key 可能不再过期。因此不能仅把两个命令连续调用就认为整体可靠,可用简短 Lua 完成组合。
每分钟最多 100 次的固定窗口,在 12:00:59 发 100 次,再在 12:01:00 发 100 次,短时间仍可能出现 200 次请求。这是窗口边界突发,不是 INCR 不原子。
滑动窗口与令牌桶
ZSet 滑动窗口可以用时间戳作分数、唯一请求标识作成员。在一个原子操作中删除窗口之前的记录,统计剩余数量,未达上限时加入新记录并设置过期时间。成员必须唯一,否则同一毫秒的多个请求可能互相覆盖,导致少计数。其内存与每用户窗口内请求数有关,超高吞吐场景要控制粒度或使用近似窗口。
令牌桶保存当前令牌数和更新时间,按流速补充至容量上限,请求消耗令牌;桶容量决定可允许的突发,补充速率决定长期速率。不要每毫秒真实往 Redis 写一次“增加令牌”,可在请求到来时按时间差计算补充。
常见追问
为什么这里通常用 Lua?
读配额、判断、增加计数和 TTL 设置需要作为一个组合操作完成,避免并发检查都通过。脚本应短小,不能长时间阻塞实例。
Redis 故障时放行还是拒绝?
按业务风险决定。公共查询可能降级到本地限流,支付或高风险操作可能限制放行;要明确降级策略,避免故障直接放大数据库流量。
所有用户共用一个限流 Key 好吗?
全局 Key 可以保护总容量,但会成为热点且无法提供用户公平性。常结合全局、租户、用户和接口维度,注意槽位与原子性需求。
使用应用时间有哪些风险?
多实例时钟偏差会影响窗口与补充计算,时间回退也需要处理。可选统一时间来源并校验时间差,同时把毫秒/秒单位定义清楚。