thinkphp 6 官方无开箱即用限流组件,需自建基于 redis 的中间件:拼唯一键(如"throttle:{$ip}:{$route}"),用 incr+expire(或 redis 6.2+的incr ex)原子计数,超阈值返回429;必须捕获redis异常降级放行,禁用file缓存,正确获取真实ip(如x-real-ip),避免cdn或代理导致误限。

ThinkPHP 6 的限流中间件不能直接用 throttle 这种 Laravel 风格的写法,官方没内置开箱即用的限流组件,得自己基于缓存 + 请求标识做,核心是控制单位时间内的请求数不超阈值。
怎么实现一个基于 Redis 的请求计数限流中间件
最常用也最可靠的方式是用 Redis 的 INCR + EXPIRE 原子操作。关键不是“写个中间件”,而是“在中间件里安全地完成一次带过期的计数检查”。
- 中间件中先拼出唯一键,比如
"throttle:{$ip}:{$route}"或只用"throttle:{$ip}" - 调用
$redis->incr($key),再立刻$redis->expire($key, $seconds)(注意:必须两步,但要尽量减少竞态;Redis 6.2+ 可用INCR EX $seconds一步完成) - 如果返回值 > 允许最大请求数(如 10),就直接
return response()->json(['msg' => '请求过于频繁'], 429) - 别用文件或 APCu 做存储——并发下不准,且无法跨机器共享
为什么不能只靠 Session 或 Cookie 做限流
Session 和 Cookie 容易被客户端伪造或绕过,且不适用于 API 场景(无会话上下文)。限流本质是服务端对「来源」的粗粒度约束,IP 是最常用维度,但要注意:Nginx 反向代理后直接读 $_SERVER['REMOTE_ADDR'] 拿到的是代理地址,得从 X-Forwarded-For 或 X-Real-IP 提取真实 IP,并确保这些 Header 不可被客户端随意篡改(Nginx 要配置 set_real_ip_from)。
- TP6 中获取真实 IP 推荐用
request()->ip(),它已内置处理常见代理头 - 如果前端有 CDN,需确认 CDN 是否透传真实 IP,否则所有请求都会被识别为 CDN 节点 IP,导致误限流
- 纯移动端 App 调用时,IP 可能大量复用(如同一基站),此时可结合
access_token或设备 ID(需客户端配合传)作为补充标识
如何避免 Redis 崩溃导致全站 500
限流不是核心业务逻辑,Redis 不可用时应降级为“不限流”,而不是让整个请求失败。中间件里所有 Redis 操作必须加 try-catch,且要有明确的 fallback 行为。
- 捕获
RedisException和Predis\Connection\ConnectionException等连接异常 - catch 后记录 warning 日志(如
Log::warning('Redis throttle unavailable')),然后return $next($request)放行 - 不要用
cache()函数封装 Redis 操作——它默认走 File 缓存,限流场景下完全失效;必须显式使用Cache::store('redis')->handler()或直接 new Redis 实例 - 如果项目用了哨兵或集群,确认中间件中初始化的 Redis 实例支持对应模式,否则
incr可能报CROSSSLOT错误
真正难的不是写几行代码,而是想清楚限流维度(IP?Token?路由?组合?)、时间窗口(滑动窗口还是固定窗口?TP6 默认只能做固定窗口)、以及降级策略是否经得起压测——比如 Redis 响应延迟升到 200ms,中间件耗时是否会让整体接口 P99 暴涨。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











