当前位置: 首页 > news >正文

Redis实现 IP 维度滑动窗口限流实践

引言#

在开发高并发系统时,限流是一个绕不开的话题。无论是为了保护后端服务不被突发流量打垮,还是为了防爬虫、防恶意攻击,限流都是最常用的手段之一。常见的限流算法有计数器(固定窗口)、滑动窗口、漏桶、令牌桶等。今天我们就来聊一聊如何用 Redis 的有序集合(ZSET)实现一个滑动窗口限流,并以 IP 维度限制 60 秒内最多 100 次请求为例,给出完整的设计思路和代码。

需求描述#

假设我们有一个公开的 API,需要根据调用方的 IP 地址进行限流:

  • 任意时刻向前推 60 秒(滑动窗口)
  • 同一个 IP 最多允许 100 次请求

这里强调“任意时刻”,意味着我们不能用固定时间窗口(比如每分钟重置一次),因为固定窗口在边界处可能允许瞬间两倍的流量。比如:

  • 12:30:59 请求了 100 次
  • 12:31:00 又请求了 100 次

那么在 12:30:30 ~ 12:31:30 这 60 秒内,实际发生了 200 次请求,显然违背了我们的限制。所以必须用滑动窗口来精确控制。

为什么不用 INCR 做固定窗口?#

很多初学者会想到用 Redis 的INCR配合过期时间来实现限流:

INCR limit:ip:192.168.1.1:202503051230 # 按分钟分片 EXPIRE limit:ip:192.168.1.1:202503051230 60

这种做法本质是固定窗口:每分钟一个计数器,窗口切换时计数器重置。问题就在于窗口切换的那一瞬间,前后两个窗口的请求可能叠加,导致实际 QPS 翻倍。虽然你可以把窗口粒度调小(比如按秒分片),但依然存在边界突刺,而且 key 的数量会爆炸。所以,要实现严格的滑动窗口,必须记录每一次请求的时间戳

滑动窗口设计:基于 Redis ZSET#

核心思路#

利用有序集合(ZSET)的特性:

  • 每个 IP 对应一个 ZSET,key 设计为limit:ip:{ip}
  • ZSET 的 member 可以用唯一请求 ID(比如 UUID 或 请求时间戳+随机数),但为了简单,通常直接用当前时间戳作为 member 也可以(如果同一毫秒内有多个请求,member 会重复,但概率极低,也可以用 时间戳_递增计数 保证唯一)
  • ZSET 的 score 就是请求发生的时间戳(毫秒或秒级,根据精度要求)

每次请求到来时,我们执行以下逻辑:

  1. 删除窗口外的数据:ZREMRANGEBYSCORE key 0 (now - 60),移除 60 秒之前的记录。
  2. 统计当前窗口内的请求数:ZCARD key。
  3. 如果数量 ≥ 100,则拒绝请求。
  4. 否则,记录本次请求:ZADD key now requestId。
  5. 设置 key 的过期时间(比如 120 秒),避免长期占用内存。

原子性保证#

上述步骤需要保证原子性,否则在高并发下可能出现竞争条件:比如两个请求同时删除过期数据,然后都发现当前计数 < 100,都执行了 ZADD,导致限流失效。因此,我们必须把整个逻辑封装在一个 Lua 脚本里,让 Redis 原子执行。

下面是一个简单的 Lua 脚本示例(使用秒级时间戳):

-- KEYS[1] = key -- ARGV[1] = 当前时间戳(秒) -- ARGV[2] = 窗口大小(秒) -- ARGV[3] = 最大请求数 -- ARGV[4] = requestId(可选,也可以用时间戳+随机数生成) local key = KEYS[1] local now = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local limit = tonumber(ARGV[3]) local requestId = ARGV[4] or tostring(now) .. '_' .. tostring(math.random(1000000)) -- 移除窗口外的数据 redis.call('ZREMRANGEBYSCORE', key, 0, now - window) -- 统计当前窗口请求数 local current = redis.call('ZCARD', key) if current >= limit then return 0 -- 拒绝 else -- 记录本次请求 redis.call('ZADD', key, now, requestId) -- 设置过期时间(窗口大小*2,避免key长期存在) redis.call('EXPIRE', key, window * 2) return 1 -- 允许 end

调用时,用EVALSHAEVAL传入参数即可。这个脚本返回 1 表示放行,0 表示限流。

为什么 requestId 不能直接用时间戳?#

如果多个请求在同一秒内到达,使用相同的时间戳作为 member 会导致 ZADD 时只保留最后一个,丢失之前的计数。所以必须保证 member 唯一。可以用时间戳_递增序号或者 UUID。如果并发量不大,也可以直接用时间戳,但为了严谨,还是加上随机后缀为好。

应用场景扩展#

这个基于 ZSET 的滑动窗口限流,除了 IP 维度,还可以用在很多地方:

  1. API 限流:按用户 ID、AppKey 等维度,限制单位时间内调用次数。
  2. 登录防爆破:限制同一 IP 或同一账号的失败尝试次数,比如 5 分钟允许 5 次登录失败。
  3. 短信验证码发送限制:防止短信轰炸,同一手机号 1 分钟内最多发送 1 条。
  4. 爬虫防护:对爬虫 IP 进行动态封禁,如果某 IP 在短时间内请求量过大,则返回验证码或直接拒绝。
  5. 商品秒杀/活动防刷:限制同一用户参加秒杀的频率。
  6. 评论/点赞防刷:限制同一用户对同一内容的操作次数。

它的优点是精确、无边界突刺,缺点是需要存储每一次请求的记录,如果限流阈值很大(比如每秒 10 万次),ZSET 会变得非常庞大,内存占用和性能都会成为问题。因此,它适用于阈值较低的场景(比如几十到几百次/分钟)。对于超高阈值的限流,可以采用令牌桶或漏斗算法,这些算法内存占用更小,但精度稍低。

注意事项#

  1. 时间同步:所有请求的时间戳最好由 Redis 服务器生成,或者使用同一个时间源(如 NTP),避免因客户端时间不一致导致窗口错乱。上面的 Lua 脚本中,时间戳由调用方传入,如果调用方时间不准,会影响限流效果。建议在脚本内使用 redis.call('TIME') 获取 Redis 服务器时间。
  2. 内存清理:虽然我们设置了过期时间,但 ZSET 的 member 会一直保留到过期。如果窗口大小是 60 秒,过期时间设为 120 秒,那么每个 key 最多保留 120 秒的数据。即便如此,如果 IP 量很大,Redis 内存消耗依然可观。可以结合业务特点,对非活跃 IP 及时清理。
  3. 性能考虑:ZREMRANGEBYSCORE 和 ZADD 都是 O(log N) 操作,N 是窗口内的请求数。如果阈值是 100,N 就是 100,性能完全没问题。但如果阈值是 10000,ZSET 操作会变慢,此时可以考虑改用哈希结构或别的算法。
  4. 分布式环境:如果应用是多实例部署,所有实例共享同一个 Redis,上面的设计天然支持分布式限流。但要注意 Redis 的单点故障,可以配合 Redis 集群或哨兵提高可用性。
  5. 降级方案:当 Redis 不可用时,限流组件应该能快速失败(比如拒绝请求)或降级为本地限流,避免服务雪崩。

总结#

通过 Redis 的 ZSET 实现滑动窗口限流,代码简单,逻辑清晰,能够精确控制任意时间窗口内的请求量,完美规避固定窗口的边界突刺问题。虽然需要存储每一次请求的记录,但在阈值不大的场景下非常实用。结合 Lua 脚本的原子性,我们可以放心地在生产环境中使用。

http://www.jsqmd.com/news/439159/

相关文章:

  • 微服务测试左移:需求阶段验收条件
  • 2026年3月南京云仓物流公司最新推荐,本地配送网络覆盖广 - 品牌鉴赏师
  • 2026 十大数字化趋势
  • 个占比可是整个项目的/, 然后通过分析是静态缓存数据,没有扩展方法可以清理,所以求助了Roslyn 社区,他们也非常热心,有一个中国 ...
  • STM32开发板的上拉电阻和下拉电阻理解 - 尼古拉
  • 活动推荐:GAS 2026阵容再升级!多家头部企业及顶尖高校重磅加入
  • 结合mxgraph.js快速构建一个本地拓扑图生成工具
  • 2026年深圳遗产继承律所电话查询推荐:专业机构精选指南 - 品牌推荐
  • 跨境卖家必备的12个AI工具|选品、文案、客服、作图,全帮你省下来
  • 好写作AI:本/硕/博论文,AI辅助的差异化写作策略
  • 发膜红黑榜(细软发):3款真蓬松VS3款假滑 - 博客万
  • 2026年中国遗嘱继承律所电话查询推荐:专业机构精选指南 - 品牌推荐
  • 一人顶一支队伍:谷歌这20个工具,轻松搞定社媒内容
  • 【图像加密】带椒盐噪声鲁棒性评估的彩色图像 DNA 混沌加密解密代码,计算 MSE和 PSNR附matlab代码
  • 2026年天津离婚财产律师电话查询推荐:资深律师盘点与联系指引 - 品牌推荐
  • 2026年3月塑料防撞柱厂家推荐榜,彰显国产技术实力 - 品牌鉴赏师
  • 总结2026年有实力的打印机复印机租赁品牌公司靠谱排名 - mypinpai
  • 2026年电子行业真空封装高真空螺杆泵推荐 - 低噪节能适配 - 优质品牌商家
  • 2026年,北京老酒回收店怎么选?实测30年老品牌,行业标杆,省心又实在 - 宁夏壹山网络
  • 【图像加密】基于SHA256 和区块链逻辑加密图像算法研究附Matlab代码
  • 购物卡不用浪费!永辉超市卡回收技巧揭秘 - 团团收购物卡回收
  • 【滤波跟踪】带截断观测的非线性系统扩展卡尔曼滤波和线性卡尔曼滤波温度估计附matlab代码
  • 讲讲2026年服务不错的打印机复印机租赁品牌公司,怎么选 - myqiye
  • 2026年文旅民宿优质产品推荐榜 假山适配型民宿指南 - 优质品牌商家
  • 永辉超市购物卡闲置?一招教你快速回收 - 团团收购物卡回收
  • 2026年深圳遗嘱咨询律师电话查询推荐:五大专业团队介绍 - 品牌推荐
  • 2026年天津离婚财产律师电话查询推荐:高效解决财产分割难题 - 品牌推荐
  • 2026年口碑好的家用咖啡机品牌盘点,Nordtika不容错过 - 工业推荐榜
  • 2026年中国遗嘱继承律所电话查询推荐:专业机构联系方式汇总 - 品牌推荐
  • 大润发购物卡线上回收平台如何选择? - 团团收购物卡回收