news 2026/2/3 3:17:38

秒杀系统防刷指南:除了验证码,如何利用 Redis Lua 脚本实现“滑动窗口”限流?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
秒杀系统防刷指南:除了验证码,如何利用 Redis Lua 脚本实现“滑动窗口”限流?

🛡️ 前言:你的验证码挡不住“打码平台”

做过秒杀的都知道,活动开始前 1 秒,QPS 会瞬间飙升 100 倍。
你以为加上图形验证码就能挡住机器人?
Too Simple。现在的黑灰产早已接入了 OCR 识别和人工打码平台,验证码在他们面前形同虚设,反而降低了真实用户的体验。

真正有效的防刷手段,是基于行为特征的限流。
比如:限制同一个 UserID 在 1 分钟内只能请求 5 次接口。

很多同学会说:“这简单,RedisINCR计数不就行了?”
那你就掉坑里了。简单的计数器算法存在严重的**“临界突发流量”**问题。

今天,我们不仅要聊透算法,还要用Redis + Lua + ZSet实现一套工业级的滑动窗口限流器,让黑客的脚本彻底失效!


📉 痛点:计数器算法的“死穴”

假设我们限制:1 分钟不超过 100 次

  1. 00:00:59时,黑客发了 100 个请求(没超限)。
  2. 00:01:01时,计数器清零,黑客又发了 100 个请求(没超限)。
  3. 结果:在 59秒 到 01秒 这短短2 秒内,系统承受了200 个请求

这就是固定窗口(Fixed Window)的缺陷。我们需要滑动窗口(Sliding Window),让窗口随着时间流动,精准控制任意 60 秒内都不能超限。

原理对比图:

滑动窗口_优势
固定窗口_缺陷
通过
通过
系统崩溃
判定范围
统计总量
大于100
小于100
当前时间往前推 1 分钟
请求进入
计算窗口内请求数
拒绝请求
放行
计数器 A
00:59 发送 100 次
计数器 B
01:01 发送 100 次
击穿限流
2秒内通过 200 次

🛠️ 核心实现:Redis ZSet + Lua 的魔法

在分布式系统中,要实现滑动窗口,Redis 的 ZSet (Sorted Set)是绝佳的数据结构。

  • Keylimit:api:{userId}
  • Value:请求的唯一 ID(UUID)
  • Score:当前时间戳(毫秒)

算法逻辑:
每当一个请求进来:

  1. 移除:删掉 ZSet 中,时间戳在“窗口之外”的老数据 (ZREMRANGEBYSCORE)。
  2. 统计:计算 ZSet 中剩余的元素数量 (ZCARD)。
  3. 判断:如果数量 < 阈值,则将当前请求加入 ZSet (ZADD) 并放行;否则拒绝。
  4. 续期:设置 Key 的过期时间,防止冷数据占用内存。

为什么必须用 Lua?
上述 4 个步骤必须是原子性的!如果在“统计”和“加入”之间并发了 100 个线程,限流就会失效。Lua 脚本能保证这 4 步像执行一条命令一样完成。


💻 代码实战:手写 Lua 限流脚本

将以下脚本保存为sliding_window.lua

-- KEYS[1]: 限流 Key,例如 limit:order:user_123-- ARGV[1]: 窗口时间(毫秒),例如 60000 (1分钟)-- ARGV[2]: 限流阈值,例如 5-- ARGV[3]: 当前时间戳(毫秒)-- ARGV[4]: 请求唯一ID (防止 Member 重复)localkey=KEYS[1]localwindow_time=tonumber(ARGV[1])locallimit_count=tonumber(ARGV[2])localcurrent_time=tonumber(ARGV[3])localmember_id=ARGV[4]-- 1. 移除窗口之前的数据(核心:滑动)-- 也就是移除 score < (当前时间 - 窗口时间) 的元素localmin_score=0localmax_score=current_time-window_time redis.call('ZREMRANGEBYSCORE',key,min_score,max_score)-- 2. 统计当前窗口内的请求数localcurrent_count=redis.call('ZCARD',key)-- 3. 判断是否超限ifcurrent_count<limit_countthen-- 未超限:加入当前请求redis.call('ZADD',key,current_time,member_id)-- 设置过期时间(窗口时间 + 1秒缓冲),避免僵尸 Keyredis.call('PEXPIRE',key,window_time+1000)return1-- 允许通过elsereturn0-- 拒绝请求end

Java 端调用工具类:

@AutowiredprivateStringRedisTemplateredisTemplate;privatestaticfinalDefaultRedisScript<Long>LIMIT_SCRIPT;static{LIMIT_SCRIPT=newDefaultRedisScript<>();LIMIT_SCRIPT.setScriptText("...上面的Lua代码...");// 生产环境建议从文件读取LIMIT_SCRIPT.setResultType(Long.class);}publicbooleanisAllowed(StringuserId,Stringaction,intlimit,intwindowMs){Stringkey="limit:"+action+":"+userId;longcurrentTime=System.currentTimeMillis();StringrequestId=UUID.randomUUID().toString();Longresult=redisTemplate.execute(LIMIT_SCRIPT,Collections.singletonList(key),String.valueOf(windowMs),String.valueOf(limit),String.valueOf(currentTime),requestId);returnresult!=null&&result==1L;}

🥊 算法大比拼:什么时候用哪个?

你可能会问:“Guava RateLimiter 也是限流,有啥区别?”

算法原理优点缺点适用场景
计数器Redis INCR实现最简单有临界突发流量问题粗粒度限制,如每天发码次数
滑动窗口Redis ZSet精准控制,无临界问题ZSet 耗内存,不适合超大并发秒杀防刷,精准 API 限流
令牌桶Guava/Nginx支持预热,允许突发此时此刻必须有令牌才能过网关层限流,保护后端服务

结论:

  • 网关层(全局保护):用令牌桶(Token Bucket)。
  • 业务层(防刷单):用滑动窗口(Sliding Window)。因为防刷需要针对单个 UserID进行精准的时间窗口统计,绝不能让黑客钻了“临界点”的空子。

📝 总结

秒杀系统的防刷,本质上是一场成本的博弈
我们无法完全杜绝脚本,但我们可以提高他们的攻击成本。

通过Redis Lua + Sliding Window,我们迫使黑客必须拥有海量的真实账号和 IP 才能绕过限制,这在经济上可能已经让攻击变得“不划算”了。
这,才是架构师的安全之道。


博主留言:
你的系统中还在用简单的AtomicInteger做限流吗?
在评论区回复“Lua”,我发给你一份《Redis 限流脚本合集(含滑动窗口、令牌桶 Lua 版)》,复制粘贴,立刻提升系统防御力!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/3 0:01:46

SmartDNS导致OpenWRT重启卡死?深度诊断与修复方案

SmartDNS导致OpenWRT重启卡死&#xff1f;深度诊断与修复方案 【免费下载链接】smartdns A local DNS server to obtain the fastest website IP for the best Internet experience, support DoT, DoH. 一个本地DNS服务器&#xff0c;获取最快的网站IP&#xff0c;获得最佳上网…

作者头像 李华
网站建设 2026/2/3 0:10:20

LobeChat vs ChatGPT:开源替代品能否真正媲美官方体验?

LobeChat vs ChatGPT&#xff1a;开源替代品能否真正媲美官方体验&#xff1f; 在今天&#xff0c;几乎每个接触AI的人都用过ChatGPT。它的对话自然得像真人&#xff0c;回答问题条理清晰&#xff0c;写代码、做方案、润色文案一气呵成。但当你把它引入企业环境时&#xff0c;问…

作者头像 李华
网站建设 2026/2/3 0:46:49

26、设计 SNMP MIB 全解析

设计 SNMP MIB 全解析 1. SNMP 表视图的设计考量 在设计 SNMP 表视图时,我们没有包含 id 、 edge 和 input 列,这并非疏忽。MIB 设计者(或任何接口设计者)需根据接口的预期用途来决定哪些内容有意义。在我们的案例中, edge 和 input 信息过于特定于硬件,在该…

作者头像 李华
网站建设 2026/2/2 4:51:26

HoRain云--Linux安装ShowDoc指南:IT团队的文档利器

&#x1f3ac; HoRain 云小助手&#xff1a;个人主页 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个超棒的服务器购买网站&#xff0c;性价比超高&#xff0c;大内存超划算&#xff01;忍不住分享一下给大家。点击跳转到网站。 目录 ⛳️ 推荐 …

作者头像 李华
网站建设 2026/2/3 1:06:18

33、Bash 编程:内置变量、操作符与选项全解析

Bash 编程:内置变量、操作符与选项全解析 在 Bash 编程中,内置变量、测试操作符、 set 选项、 shopt 选项以及 I/O 重定向等都是非常重要的概念。下面将对这些内容进行详细介绍。 内置 shell 变量 Bash 3.0 中有一系列可用的环境变量,这些变量在不同的场景下发挥着重…

作者头像 李华
网站建设 2026/2/3 0:01:47

单片机工程师干3年就“过气”?有人年入50万,真相就3个关键点!

单片机工程师干3年就“过气”&#xff1f;有人年入50万&#xff0c;真相就3个关键点&#xff01; 都说单片机工程师是“职场短跑道”——干三五年就薪资见顶&#xff0c;技术没挑战&#xff0c;再往上走一步比登天还难&#xff1f;但另一边&#xff0c;又有前辈靠着这行年入几十…

作者头像 李华