<p>Nginx access_by_lua* 阶段结合 Redis 实现动态防刷是最灵活严密的方式,分精准识别、原子计数、即时响应三步闭环,并需连接池管理、降级策略与多维校验保障稳定。</p>

直接在 Nginx 的 access_by_lua* 阶段用 Lua 控制请求放行或拦截,是目前最灵活、可定制性最强的防刷方式。它不依赖静态规则,能结合实时状态(如 Redis 计数)、多维条件(IP+UA+Token+参数特征)做动态判断,比原生 limit_req 更严密。
核心逻辑:先识别再计数再决策
防刷不是简单“限速”,而是分三步闭环:
-
精准识别主体:优先取
X-Real-IP, fallback 到X-Forwarded-For或remote_addr;对 API 接口还可叠加Authorization头、app_id参数等维度,避免单靠 IP 被绕过 -
原子化计数与判断:用 Redis 的
EVAL执行 Lua 脚本,保证“读-增-判-设过期”四步不可分割。例如:1 分钟内该 IP+AppID 组合请求超 30 次,就写入block:ip:app键,TTL 设为 10 分钟 -
即时响应与反馈:命中封禁直接
ngx.exit(429)或ngx.exit(403),并可自定义响应头(如X-RateLimit-Remaining)或返回 JSON 提示,便于前端友好降级
关键配置:Nginx + OpenResty + Redis 协同
确保你使用的是 OpenResty(含 lua-resty-redis),并在 http 块中预加载连接池:
- 在
nginx.conf的http块里配置 Redis 连接池参数,避免每次新建连接 - 在
location块中调用access_by_lua_file /path/to/anti-flood.lua,不建议写内联脚本,便于维护和热更新 - 脚本中必须包含
close_redis()逻辑:无论成功失败,都要调用set_keepalive归还连接,否则连接池会耗尽 - Redis 故障时要有降级策略,比如
red == nil or err时默认放行(fail-open),避免雪崩
增强严密性的实战技巧
单纯按 IP 限频容易被代理池或 CDN 后的真实用户误伤,需叠加多层校验:
-
行为指纹:检查
User-Agent是否为空、是否含爬虫关键词(如python-requests、curl),或是否缺失常见浏览器头(Accept-Language、Sec-Ch-Ua) -
参数一致性:对带签名的接口,用 Lua 校验
X-Sign头——提取请求参数、按约定排序拼接、加盐哈希,比对失败即拦截 - 时间窗口平滑:不用固定窗口(如每分钟清零),改用滑动窗口逻辑(Redis ZSET + 时间戳 score),更精准防突刺流量
- 分级响应:首次超限返回 429 并提示“稍后再试”;连续 3 次触发则写入长期封禁键(TTL 24h),实现灰度封禁
避坑提醒:别让防刷变成单点故障
严密不等于复杂,稳定才是前提:
- Redis 必须部署为单实例高可用(如 Docker+Marathon 秒级拉起),不能成为瓶颈;禁用 RDB/AOF,因防刷数据本身不要持久化
- Lua 脚本执行时间要控制在毫秒级,避免
ngx.sleep()或复杂循环;高频接口建议用lua-resty-limit-traffic模块替代手写计数 - 务必配置白名单机制(如通过
geo+map指令),运维 IP、监控探针、CDN 回源地址必须豁免,否则自己把自己锁死 - 所有拦截动作必须记录到日志(
ngx.log(ngx.WARN, ...)),并同步推送至 ELK 或 Prometheus,用于复盘和规则调优


















