不能直接用,TP8已移除内置RateLimiter组件,需手写Lua+Redis限流中间件,避免INCR/EXPIRE竞态问题,并注意key构造、中间件顺序及Redis连接池配置。

RateLimiter 组件在 TP8 里还能用吗?
不能直接用。ThinkPHP 8.0 已移除 think-redis 和内置 RateLimiter 组件,官方不再维护基于 Redis 的限流中间件——这不是 bug,是主动剥离。你如果照搬 TP6 的 RateLimiter::attempt(),会抛出 Class "think\facade\RateLimiter" not found 错误,且不会 fallback 到内存或静默失效,而是直接报错中断请求。
必须自己实现,且推荐手写 Lua + Redis 中间件,而非依赖第三方扩展包(多数未适配 TP8 的容器绑定和 Request 生命周期)。
为什么必须用 Lua 脚本,不能用 INCR + EXPIRE?
因为 INCR 和 EXPIRE 是两个独立命令,高并发下必然出现竞态:100 个请求同时读到剩余令牌为 1,全部通过判断后执行 DECR,结果 99 个超发。这不是“偶尔出错”,而是压测时必现的漏放。
TP8 的 Redis::eval() 支持原生 Lua 调用,但要注意:argv 全是字符串,tonumber(argv[1]) 必须显式转换,否则 if remaining > 0 永远为 false;返回值必须是数组,例如 return {allowed, remaining},否则中间件拿不到剩余令牌数用于调试。
立即学习“PHP免费学习笔记(深入)”;
示例关键片段:
local capacity = tonumber(argv[1])
local rate_per_sec = tonumber(argv[2])
local now_ms = tonumber(argv[3])
local state = redis.call('hmget', keys[1], 'last_fill_ms', 'tokens')
local last_fill_ms = tonumber(state[1]) or now_ms
local tokens = math.min(capacity, tonumber(state[2]) or capacity + (now_ms - last_fill_ms) * rate_per_sec / 1000)
if tokens < 1 then return {0, 0} end
redis.call('hset', keys[1], 'last_fill_ms', now_ms, 'tokens', tokens - 1)
return {1, tokens - 1}key 设计踩哪些坑会导致限流失效?
TP8 不再自动拼接 $request->url(true) . $request->ip(),你得自己构造 key。常见错误:
-
rate:api/v1/user/info:127.0.0.1里含斜杠和冒号 —— Redis 不拒绝,但某些驱动(如 predis)解析失败,建议统一替换为下划线:str_replace(['/', ':', '?'], '_', $key) - 按
$auth->id构造 key,但游客请求没登录,$auth->id为空 → 所有游客共用一个桶 → 一人刷崩全站,必须 fallback:$userId ?: $request->ip() - key 里混入动态参数,比如
rate:api/v1/user/123/info→ 每个用户 ID 单独一个桶,但攻击者可批量换 ID 绕过,应固定为rate:api/v1/user/info+ 用户标识
中间件注册顺序和降级逻辑怎么写才不翻车?
TP8 的中间件执行链更严格,顺序错一环就白干:
- 必须在
CorsMiddleware::class之后注册 —— 否则跨域预检请求(OPTIONS)还没过 CORS 就被限流拦截,前端收不到 204,直接报 CORS error - 必须在
AuthMiddleware::class之前 —— 否则未鉴权的恶意请求先过限流,再被鉴权拦掉,浪费 Redis 原子操作,还可能因 key 为空导致桶混乱 - Redis 连接失败时不能 throw Exception —— 会中断整个请求生命周期,应捕获
RedisException,记录日志,并返回true(放行),避免雪崩
真正容易被忽略的是:TP8 默认关闭 Redis 连接池复用,每次中间件调用都新建连接,高频接口下 Redis 连接数飙升。务必在 config/redis.php 中开启 'pool' => ['enable' => true],否则单机扛不住 500 QPS。



















