半开状态不会自动进入,因php无状态且熔断器实例存于内存,需在fallback中手动调用proberequest()并配合cantrynextrequest()判断时间窗口,成功后渐进式放量。

半开状态不会自动恢复,必须手动触发试探请求,否则熔断器永远卡在 open 状态。
为什么 half_open 不会自动进入?
PHP 是无状态的,CircuitBreaker 实例默认存在内存里,每次请求都是新对象。即使你设了 60 秒超时,也没人去检查“现在是否该试探”。库本身不启动定时器或后台任务——那是 Swoole 或队列的事,不是熔断器的职责。
-
thephpleague/circuit-breaker和php-circuit-breaker都只提供状态判断接口(如canTryNextRequest()),不自动调用 - 所谓“休眠后自动进 half_open”,其实是伪命题:没人调
canTryNextRequest(),状态就永远不会变 - FPM 下尤其明显:一个 worker 进入
open,其他 worker 完全不知道,各自计数,熔断阈值形同虚设
怎么正确触发半开试探?
必须在降级逻辑(fallback)里显式调用一次探针请求,且和主调用隔离。
- 不要在
catch块里直接重试原接口——这会污染失败统计,还可能绕过熔断器封装 - 单独写一个
probeRequest()方法,用同样熔断器实例、但跳过业务逻辑校验(比如不验resp.code === 0) - 调用前先
$breaker->canTryNextRequest()判断是否满足时间窗口(如距上次失败 ≥ 60 秒) - 试探成功后调
$breaker->recordSuccess(),失败则$breaker->recordFailure() - 别忘了把状态存 Redis:键名建议用
circuit_state:api_order_submit,字段含state、last_failure_time、failure_count
Http::get() 调用后熔断没反应?检查这三点
ThinkPHP 的 think\facade\Http 默认吃掉所有错误,返回 Response 对象,熔断器根本收不到失败信号。
- 必须加
failException(true):例如Http::timeout(3)->failException(true)->get($url),否则超时/502 都不抛异常 - HTTP 状态码正常(如 200)但业务失败(如
{"code":5001}),得在回调里手动$breaker->recordFailure() - 别用
try/catch包裹整个 HTTP 调用再通知熔断器——recordFailure()必须在异常发生瞬间执行,否则并发下计数错乱
试探成功后立刻全量放行?危险
下游服务可能只是短暂恢复,或尚未从连接池重建、缓存预热中缓过来。一上来就涌进 100 QPS,大概率二次击穿。
- 不要直接
setState('closed'),而是进一个中间态(如rampup),配合滑动窗口统计最近 5 次试探成功率 - 初始只允许每秒 1 次请求,每 30 秒 +1,直到回到原始限流值(如 50 QPS)
- 这个渐进逻辑不能抽成全局中间件——不同接口的下游承载力不同,必须绑定到具体
$breaker实例上 - Redis 缓存过期时间要设为
熔断 timeout + 10 秒,避免状态刚进half_open就被清掉
最容易被忽略的一点:半开试探不是“健康检查”,它本质是一次真实业务请求。probe 接口必须和线上路径一致、参数一致、鉴权一致,否则试探通过了,正式请求仍可能失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











