协程熔断不能直接套用同步逻辑,因sleep或轮询会阻塞协程调度,且实例变量无法跨协程共享失败计数;须用Swoole\Table存状态并配合Atomic控制半开探测,避免竞态与资源泄漏。

协程熔断为什么不能直接套用同步逻辑
在 Swoole 协程环境下,go 启动的协程是轻量级、非阻塞的,但传统熔断器(比如基于 sleep() 或全局变量轮询)会破坏协程调度——一旦某个协程卡在“等待熔断冷却”,它就不再让出控制权,导致整个协程调度器被拖慢甚至假死。更关键的是,协程间默认不共享状态,$failureCount 这类变量若放在类实例里,每个协程都有一份副本,根本无法统计全局失败率。
用 Swoole\Table 实现跨协程共享熔断状态
必须把熔断器的状态(失败次数、上次失败时间、当前状态)存到进程级共享内存中,Swoole\Table 是最稳妥的选择。它支持原子操作、多协程并发读写,且无需加锁。
- 定义表结构:
new Swoole\Table(1024),字段包括fail_count(int)、last_fail_time(int)、state(string,值为"closed"/"open"/"half-open") - 每次调用前查表判断:
$table->get($serviceKey),根据state和时间差决定是否放行 - 失败时原子递增:
$table->incr($serviceKey, 'fail_count'),并更新last_fail_time - 成功时重置:
$table->set($serviceKey, ['fail_count' => 0, 'state' => 'closed'])
半开状态下如何安全试探下游服务
进入半开状态后,不能让所有协程同时发起探测请求——这等于把压力又压回下游。正确做法是只允许一个协程执行探测,其余协程等待其结果再统一切换状态。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 用
Swoole\Atomic控制探测权:$atomic->add()成功者获得唯一探测资格 - 探测协程调用下游后,根据结果调用
$table->set()更新全局状态 - 其他协程在
get()返回"half-open"后,应co::usleep(10000)短暂等待再重查表,避免忙等 - 注意:探测请求本身也要设超时,比如
['timeout' => 1.5],防止半开态卡死
别忽略协程生命周期与资源泄漏
熔断器对象本身如果绑定了协程上下文(比如闭包里捕获了 $this),又没做清理,容易引发内存泄漏——尤其在高频调用场景下,协程退出但对象仍被引用,GC 无法回收。
- 避免在熔断逻辑里保存长生命周期对象引用,如
$client实例 - 所有 HTTP/MySQL 客户端必须显式调用
close()或依赖连接池自动回收 - 使用
defer确保状态重置:defer(function () use ($table, $key) { $table->del($key); });不适用,因为状态要跨协程持久化;真正该 defer 的是临时资源释放
真正难的不是写个三态开关,而是让状态在十万并发协程里准确、低开销地同步——Swoole\Table 是目前最接近“开箱即用”的解法,但必须亲手验证它的原子性和边界行为,比如并发 incr + set 组合是否真能避免竞态。

















