redis分布式计数器防刷限流核心是利用单线程原子性与全局内存共享,常用方案为固定窗口计数器(适合精度要求不高的场景),通过incr+expire实现每秒独立计数;更精准可用滑动窗口(zset维护时间戳列表);生产需注意key粒度、过期时间略长于窗口、统一时间源及lua脚本保障原子性;突发流量平滑需求则宜选令牌桶。

Redis 实现分布式计数器防刷与限流,核心是利用其单线程原子性 + 全局内存共享特性,在多节点环境下统一统计请求频次。最常用、最轻量的方案是固定窗口计数器,适合登录、API 防刷、IP 限流等对精度要求不苛刻但需简单高效的场景。
固定窗口计数器(推荐入门用)
把时间切成等长片段(如每秒一个窗口),每个窗口内独立计数,超阈值就拦截。
-
Key 设计要带时间标识:比如
limit:ip:192.168.1.100:1723004800,其中1723004800是当前秒级时间戳(System.currentTimeMillis() / 1000),确保每秒键名不同 -
原子操作靠 INCR + EXPIRE:先
INCR key自增,若返回值为 1(首次写入),立刻EXPIRE key 1设 1 秒过期——这样既保证窗口准时切换,又避免键长期残留 -
判断逻辑在应用层:拿到自增后数值,直接和阈值比,比如每秒最多 5 次,
count > 5就拒绝
滑动窗口计数器(需要更精准控制)
解决固定窗口在边界处突增的问题(例如第 1 秒末冲进 5 次,第 2 秒初再冲进 5 次,2ms 内就达 10 次)。本质是按毫秒/秒粒度维护一个“最近 N 秒”的请求时间列表。
-
用 Redis ZSet 实现:score 存时间戳(毫秒),value 用唯一标识(如 UUID 或请求 ID),每次请求执行
ZADD key timestamp uuid -
清理旧数据 + 统计数量:用
ZREMRANGEBYSCORE key 0 (current_timestamp - window_ms)清掉窗口外记录,再用ZCARD key获取当前窗口请求数 - 注意内存增长:ZSet 不自动过期,得靠定时清理或业务侧主动裁剪;也可配合 Lua 脚本把清理+统计封装成原子操作
生产中必须注意的细节
光有算法不够,几个关键点直接影响是否真正生效:
-
Key 的粒度要合理:防刷通常按
用户ID或IP区分;秒杀类接口可能还要加商品ID,避免全局限流误伤正常用户 -
过期时间略大于窗口时长:比如 1 秒窗口,设
EXPIRE key 1001毫秒,防止因网络延迟导致新窗口已开始、旧键还没删干净 - 不要依赖本地时间做窗口切分:所有服务应使用统一时间源(如 NTP 同步),否则各节点算出的时间戳不一致,窗口错位
- 高并发下建议用 Lua 脚本:把 INCR、EXPIRE、比较阈值等多步操作打包进一个脚本执行,彻底避免竞态,Redis 保证脚本内命令原子执行
什么时候该换令牌桶?
如果业务允许短时突发(比如用户点一次刷新,前端发了 3 个请求),且希望流量更平滑,固定/滑动窗口的“硬截断”体验不好,那就该上令牌桶。Redisson 提供开箱即用的 RRateLimiter,底层用 Lua 实现令牌生成与消耗,支持预热、公平队列等高级特性,比手写更稳。











