ThinkPHP 8.1需自主实现分布式限流熔断:限流必须用Redis+Lua原子脚本避免竞态;熔断基于Redis Hash滑动窗口动态决策;中间件串联二者,按熔断→限流顺序执行并透传状态。

ThinkPHP 8.1 本身不提供开箱即用的分布式限流熔断组件,官方已移除 think-redis 和内置 RateLimiter,必须自主实现。核心难点不在“做不做”,而在“如何避免竞态、保证原子性、适配 TP8 生命周期”。下面分三块讲清楚:限流、熔断、协同落地。
限流:必须用 Lua 脚本 + Redis 原子操作
不能用 INCR + EXPIRE 组合——高并发下必然超发。TP8 的 Redis::eval() 是唯一可靠入口,且需注意:
- Lua 中所有
argv都是字符串,tonumber(argv[1])必须显式转换,否则数值比较永远失败 - 返回值必须是数组,如
return {allowed, remaining},中间件才能读取剩余令牌数用于日志或响应头 - Key 设计要避开特殊字符:把
$request->url(true) . '_' . $request->ip()中的/:?全部替换为下划线,否则某些 Redis 驱动(如 predis)解析失败
熔断:用 Redis Hash 存状态,配合请求成功率滑动窗口
熔断不是“开关”,而是基于最近 N 次请求的成功率动态决策。推荐用 Redis Hash 存储三个字段:success_count、fail_count、last_reset_ms,每请求一次就更新一次。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 窗口周期设为 60 秒,用
EXPIRE自动清理;每次访问先检查是否过期,过期则重置计数器 - 成功率 =
success_count / (success_count + fail_count),低于阈值(如 60%)且失败次数 ≥5,则开启熔断(写入state:open) - 熔断后新请求直接拒绝,但每 30 秒允许一个试探请求(half-open 状态),成功则关闭熔断,失败则重置倒计时
中间件里串联限流与熔断逻辑
在自定义中间件中按顺序执行判断,失败立即中断,不进控制器:
立即学习“PHP免费学习笔记(深入)”;
- 先查熔断状态:若为
open,直接返回 429 或 503,不走限流 - 再执行限流 Lua 脚本:传入 key、容量、速率(如 100 次/分钟)、当前毫秒时间戳
- 若限流拒绝,记录失败计数并触发熔断更新;若通过,记录成功计数
- 响应头中可带
X-RateLimit-Remaining和X-Circuit-State,方便前端或网关感知
不复杂但容易忽略。关键不是堆功能,而是让每个环节都可验证、可回溯、不依赖全局变量或内存缓存。


















