ThinkPHP 6 的 RateLimiter 组件必须依赖 Redis 才能生效,不配置 Redis 则限速完全失效;需确保 Redis 连接正常、驱动配置正确、key 粒度合理、令牌桶参数匹配接口耗时,并将中间件注册在跨域之后、鉴权之前。

ThinkPHP 6 的 RateLimiter 组件必须连 Redis 才能生效,不配 Redis 就等于没开限流——它底层所有令牌桶状态都依赖 Redis 的原子操作,换内存或文件驱动会直接失效。
RateLimiter 不生效?先确认 Redis 连得上、配得对
常见错误现象:RateLimiter::attempt() 总返回 false,但没报错;或者压测时完全不拦截,日志里也看不到 429 记录。
- 检查
config/cache.php中'default' => 'redis'是否已设为 redis 驱动 - 确认
config/redis.php里的host、port、password和database全部填对,尤其是内网环境常漏配database - 别用 phpredis 4.3.0 以下版本——某些 Lua 脚本(比如补令牌逻辑)会执行失败,建议升到 5.3.7+
- 测试前先用
redis-cli -h {host} -p {port} -a {password}连上去敲PING,再跑KEYS rate:*看桶 key 是否真写进去了
key 粒度不对,限速就乱套
ThinkPHP 默认用 $request->url(true) . $request->ip() 拼 key,结果是“每个 IP + 完整 URL”单独计数。但你要的往往是“所有 /api/v1/user/info 请求共用一个桶”,或者“按登录用户 ID 限速”。
- 重写
app/middleware/RateLimitMiddleware.php中的getCacheKey()方法,改用$request->rule()->getName()截取路由名,避免带参数的 URL 导致 key 泛滥 - 若按用户限速,确保 token 已解析出
user_id,key 里拼user_id;但未登录请求必须 fallback 到 IP,否则游客全被堵死 - key 里别留斜杠、冒号、问号——
str_replace(['/', ':', '?'], '_', $key)预处理一下更稳妥,Redis 对 key 名字符敏感
令牌桶参数不是拍脑袋设的
$maxAttempts 和 $decayMinutes 设错,会导致桶刚填满就被掏空,用户频繁收到 429,或者限速形同虚设。
立即学习“PHP免费学习笔记(深入)”;
- 先压测接口 P95 耗时,比如是 800ms,那理论每分钟最多撑住
60 / 0.8 ≈ 75次,$maxAttempts设 80 比设 100 更合理 -
$decayMinutes = 1是常规选择,但突发流量场景可改成0.1(即 6 秒),让桶更快恢复,减少误杀 - Redis 中桶的过期时间由
$decayMinutes决定,不是固定 TTL,别手动用EXPIRE去调,会干扰算法
中间件注册顺序错了,限速就白搭
RateLimiter 必须在跨域中间件之后、鉴权中间件之前注册,否则要么被 CORS 拦截前就丢了,要么未鉴权就放行了恶意请求。
- 检查
app/middleware.php或路由级中间件配置,确保RateLimitMiddleware::class出现在CorsMiddleware::class之后、AuthMiddleware::class之前 - 如果用了全局中间件,又在某个路由上单独加了鉴权,记得该路由要显式声明 RateLimiter,别指望全局顺序能覆盖局部逻辑
- Token 解析(比如从 Bearer 提取 uid)必须在 RateLimiter 执行前完成,否则
$request->user()取不到值,key fallback 失效
真正难的不是写几行代码,而是把 key 粒度、桶参数、中间件位置这三件事对齐业务真实流量模型——比如用户 ID 限速时,token 解析失败的降级路径是否兜得住,这点最容易被忽略。



















