商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > TP8.0 接口被恶意刷量?采用滑动窗口算法实现精准限流防护

TP8.0 接口被恶意刷量?采用滑动窗口算法实现精准限流防护

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

TP8.0项目如何用滑动窗口限流,精准拦截恶意刷量

接口被恶意刷量导致服务器响应变慢、短信验证码被批量盗取、支付接口被反复试探——这些问题在TP8.0项目中必须立刻拦截。传统的固定窗口限流最大的漏洞在于:窗口切换瞬间允许双倍请求通过,这根本挡不住有准备的攻击者。所以,滑动窗口方案才是更靠谱的选择。

不过,在动手实现之前,有几个前提条件必须先确认,否则后面写的Lua脚本可能根本不会生效。

确认滑动窗口限流的适用前提

第一步,先确认一下环境是否就绪——执行 php -m | grep redis 看看。如果没有任何输出,说明还没装扩展,那就得先安装 phpredis,然后重启PHP-FPM。

第二步,检查Redis连接配置是否写在了 config/cache.php 中,确认 redis 驱动项下的 hostportpassword(如果有)全部可连通。这一点很多人容易忽略——未验证Redis连通性就写Lua脚本,会导致限流逻辑完全不生效,排查起来还特别费劲。

第三步,确保目标接口已经注册为中间件路由。比如在 app/middleware.php 中绑定 AppMiddlewareRateLimitMiddleware::class 到对应的路由组。这一步漏掉了,限流中间件根本不会触发。

用ZSET实现滑动窗口计数(核心方案)

滑动窗口的核心思路并不复杂:在Redis里为每个用户或IP生成一个唯一key,比如 rate:login:192.168.1.100。每次请求过来时,以毫秒级时间戳为score,随机字符串为member,执行 ZADD key now randstr;然后清理1秒前的数据,用 ZREMRANGEBYSCORE key 0 (now-1000);最后 ZCARD key 获取当前窗口内的请求数。

方案一如果直接写在PHP代码里,会存在原子性问题——清理和计数不是一步完成,高并发下容易出现数据不一致。行业里的共识是,用Lua脚本把原子性保证交给Redis去处理,这才是靠谱的做法。

推荐方案二:将以下脚本保存为 sliding_window.lua

local key = KEYS[1]
local window_ms = tonumber(ARGV[1])
local max_count = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call("ZREMRANGEBYSCORE", key, 0, now - window_ms)
local count = redis.call("ZCARD", key)
if count < max_count then
  redis.call("ZADD", key, now, tostring(now) .. ":" .. math.random(1000,9999))
  redis.call("PEXPIRE", key, window_ms + 1000)
  return 1
else
  return 0
end

在TP8.0中间件中这样调用:$this->redis->eval(file_get_contents('sliding_window.lua'), [$key, 1000, 5, microtime(true)*1000])。这里1000表示1秒窗口,5是阈值,毫秒级时间戳保证了计数精度。关键在于Lua脚本的原子性——清理、检查、添加三步在Redis内部一次性完成,不会出现竞态条件。

按场景配置不同限流策略

不同类型的接口,限流策略肯定不能一刀切。

登录接口:建议用IP+手机号的组合key,窗口设10秒、阈值10次,防止暴力撞库。注意手机号需要脱敏拼接,避免明文泄露风险。

短信发送接口:用手机号单维度key,窗口60秒、阈值2次,同时叠加图形验证码前置校验。这样一来,即便攻击者拿到了手机号列表,也无法短时间内大量触发短信。

支付回调接口:用商户号+订单号的复合key,窗口300秒、阈值1次,专门防重放攻击。不过这里要特别提醒——限流只能控制频率,不能替代业务层的幂等性校验。必须开启幂等性去重,否则限流只是治标不治本

验证限流是否生效

方案写好了,怎么知道它真的在干活?最简单的办法是用ab命令压测:ab -n 20 -c 10 "http://yourdomain.com/api/login",观察返回状态码的分布。正常情况下,部分请求会返回429状态码,这就是限流生效的信号。

手动检查Redis也能确认:redis-cli zrange rate:login:127.0.0.1 0 -1 WITHSCORES,看看ZSET中是否只有最近1秒内的成员。如果发现旧数据还在,说明清理逻辑可能没跑起来。

最后,查看TP8.0日志:runtime/log/202607/07_rate_limit.log,搜索关键词 rejected_by_sliding_window,确认拦截记录持续写入。日志里有每一条被拒绝的请求细节,这才是判断限流是否生效的最可靠依据。

本文转载于:https://www.php.cn/faq/2809062.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注