PHP接口限流,基于Redis滑动窗口实现防刷接口

胖涛姑娘_7146

胖涛姑娘_7146

2026-10-02

723人浏览

原创

必须用lua脚本实现滑动窗口限流,因incr+expire非原子操作会导致高并发下竞态失效;脚本需原子执行清理、统计、判断、写入四步,并正确设置过期防止内存泄漏。

php接口限流,基于redis滑动窗口实现防刷接口

直接用 INCR + EXPIRE 两步走做滑动窗口,必然出错——这不是性能问题,是逻辑缺陷。必须用 Lua 脚本把「清理过期数据 → 统计数量 → 判断是否超限 → 写入新时间戳」这四步锁死为原子操作。

为什么不能用 PHP 自增 + 过期分两步

高并发下,两个请求几乎同时进来:第一个刚 INCR 完但还没 EXPIRE,第二个就查到旧值并误判为“未超限”,结果双双放行。固定窗口还能靠窗口重置勉强兜底,滑动窗口依赖精确时间范围,这种竞态会导致限流完全失效。

  • Redis 的 INCR 和 EXPIRE 不是原子的,中间存在时间窗口
  • PHP 层无法保证多进程/多协程间对同一 key 的操作顺序
  • 哪怕加了 mutex 锁,也会严重拖慢吞吐,失去 Redis 限流的意义

滑动窗口 Lua 脚本关键参数和行为

脚本接收 KEYS[1](限流 key)、ARGV[1](当前毫秒时间戳)、ARGV[2](窗口毫秒长度)、ARGV[3](最大请求数)四个参数,核心逻辑是:

  • ZREMRANGEBYSCORE 清理所有早于 ARGV[1] - ARGV[2] 的记录
  • ZCARD 获取当前窗口内剩余请求数
  • 未超限时,用 ZADD 插入 ARGV[1] 作为 score、一个唯一标识(如 microtime(true).rand(1000,9999))作为 member
  • 必须调用 EXPIRE 设置 key 过期时间为 ceil(ARGV[2]/1000) + 1 秒,防止 zset 残留导致内存泄漏

示例脚本片段:

btpanel phpsite 宝塔面板PHP网站
btpanel phpsite 宝塔面板PHP网站

宝塔面板 PHP 网站管理:站点创建、删除、启停、PHP 版本切换、域名管理、SSL证书管理、伪静态管理、数据库管理

下载
local window_start = tonumber(ARGV[1]) - tonumber(ARGV[2])
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, window_start)
local current_count = redis.call('ZCARD', KEYS[1])
if current_count <h3>IP 限流时真实客户端识别容易翻车</h3><p>直接读 <code>$request->client->host</code> 或 <code>$_SERVER['REMOTE_ADDR']</code>,在 Nginx、Cloudflare、SLB 后面拿到的几乎肯定是代理 IP,不是用户真实出口 IP。</p>
  • 必须检查 X-Forwarded-For 或 X-Real-IP,但仅信任你可控的上游代理头(比如只认 Nginx 配置里明确 set 的那个)
  • 更稳妥的做法是优先按 user_id 限流,登录态缺失时再 fallback 到 IP,并对 IP 做可信链校验
  • 如果业务允许,用设备指纹(如 UA + IP + TLS 指纹哈希)替代纯 IP,能显著降低误杀率

PHP 调用 EVAL 执行限流脚本的实操要点

别封装过度,直接用 Redis::eval() 最稳。注意三点:

  • 脚本内容建议放在单独文件(如 resources/lua/sliding_window.lua),用 file_get_contents() 加载后传入,避免字符串拼接出错
  • key 名要带业务上下文,比如 "api:login:ip:".$realIp 或 "api:sms:phone:".$phone,避免不同接口共用 key 导致误限
  • 返回值是整数:1 表示放行,0 表示被限,不要当成布尔值直接 if 判断(Lua 返回 nil 或 number,PHP 可能类型转换异常)

调用示例:

$lua = file_get_contents(__DIR__.'/../lua/sliding_window.lua');
$result = $redis->eval($lua, [$key], 3, $nowMs, $windowMs, $limit);

滑动窗口真正难的不是写脚本,而是 key 的粒度设计和客户端识别链路的可靠性——同一个用户换网络、开多个标签页、走不同 CDN 节点,都可能被算作不同请求源。上线前务必用真实代理链路压测,别只在本地 curl 127.0.0.1 验证。

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
php文件怎么打开
php文件怎么打开

打开php文件步骤:1、选择文本编辑器;2、在选择的文本编辑器中,创建一个新的文件,并将其保存为.php文件;3、在创建的PHP文件中,编写PHP代码;4、要在本地计算机上运行PHP文件,需要设置一个服务器环境;5、安装服务器环境后,需要将PHP文件放入服务器目录中;6、一旦将PHP文件放入服务器目录中,就可以通过浏览器来运行它。

2023.09.01

9704

6

php怎么取出数组的前几个元素
php怎么取出数组的前几个元素

取出php数组的前几个元素的方法有使用array_slice()函数、使用array_splice()函数、使用循环遍历、使用array_slice()函数和array_values()函数等。本专题为大家提供php数组相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

5821

5

php反序列化失败怎么办
php反序列化失败怎么办

php反序列化失败的解决办法检查序列化数据。检查类定义、检查错误日志、更新PHP版本和应用安全措施等。本专题为大家提供php反序列化相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.11

2055

5

php怎么连接mssql数据库
php怎么连接mssql数据库

连接方法:1、通过mssql_系列函数;2、通过sqlsrv_系列函数;3、通过odbc方式连接;4、通过PDO方式;5、通过COM方式连接。想了解php怎么连接mssql数据库的详细内容,可以访问下面的文章。

2023.10.23

3628

4

php连接mssql数据库的方法
php连接mssql数据库的方法

php连接mssql数据库的方法有使用PHP的MSSQL扩展、使用PDO等。想了解更多php连接mssql数据库相关内容,可以阅读本专题下面的文章。

2023.10.23

4334

6

html怎么上传
html怎么上传

html通过使用HTML表单、JavaScript和PHP上传。更多关于html的问题详细请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.03

3391

9

PHP出现乱码怎么解决
PHP出现乱码怎么解决

PHP出现乱码可以通过修改PHP文件头部的字符编码设置、检查PHP文件的编码格式、检查数据库连接设置和检查HTML页面的字符编码设置来解决。更多关于php乱码的问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.09

4817

8

php文件怎么在手机上打开
php文件怎么在手机上打开

php文件在手机上打开需要在手机上搭建一个能够运行php的服务器环境,并将php文件上传到服务器上。再在手机上的浏览器中输入服务器的IP地址或域名,加上php文件的路径,即可打开php文件并查看其内容。更多关于php相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.13

3762

8

sprintf函数用法详解
sprintf函数用法详解

sprintf函数的用法:1、格式化字符串;2、指定输出宽度和精度;3、返回值。更多关于sprintf函数用法详解的内容,大家可以阅读下面的文章。

2023.11.27

11762

4

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
《20天入门精通PHP》视频教程
《20天入门精通PHP》视频教程

共200课时 | 48.1万人学习

墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习