不能靠try-catch+计数器简单实现熔断,因熔断需跨进程状态共享、滑动时间窗口统计、超时与业务错误区分、自动冷却及半开探测机制,而单进程计数、无状态持久化、不识别超时、无恢复验证等导致其失效。

Workerman 本身不提供开箱即用的熔断器,必须手动集成或自建状态机;直接在 onMessage 或服务调用处硬编码 sleep() 或简单计数,无法应对并发、超时、半开探测等真实场景。
为什么不能靠 try-catch + 计数器简单实现熔断?
熔断不是“失败 5 次就停用”,它需要状态隔离、时间窗口统计、自动恢复探测和跨进程一致性。常见错误包括:
- 只在单个 Worker 进程内计数 → 集群下熔断失效
- 未区分超时(
Connection timed out)与业务错误(如404)→ 错误率失真 - 进入
Open状态后不设冷却时间,或冷却后直接全量放行 → 可能瞬间压垮下游 - 没实现
Half-Open状态 → 无法自动验证服务是否恢复,只能靠人工重启
推荐用 php-resilience/circuit-breaker 集成到 Workerman 调用链
该库提供标准三态状态机、滑动时间窗口、可配置失败判定(支持异常类、HTTP 状态码、超时阈值),且默认支持内存存储,适合单机部署;若需集群共享状态,可搭配 Redis 驱动。
实操建议:
- 将外部服务调用(如
AsyncTcpConnection、curl_init、Redis::connect)统一包装进一个方法,例如callPaymentService() - 在该方法入口使用
CircuitBreaker::execute()包裹实际调用逻辑 - 配置关键参数:
failureThreshold(如 0.5 表示错误率 50%)、timeout(如 60 秒)、minimumThroughput(最小请求数,避免低流量误熔断) - 捕获
CircuitBreakerOpenException并返回降级响应(如缓存数据、默认值),而非抛给onMessage上层
如何让熔断状态在 Workerman 多进程间同步?
Workerman 默认每个 Worker 是独立 PHP 进程,static 变量或内存状态无法共享。强行用文件锁或信号同步极易出错。
更可靠的做法:
- 单机多 Worker:用
Redis存储熔断状态(key 格式如circuit:payment_service:state),所有进程读写同一份数据 - 集群多机器:仍用 Redis(推荐 Redis Cluster 或哨兵模式),配合原子操作(
INCR、GETSET)更新失败计数和时间戳 - 避免轮询:不要在每次请求都
GET状态;可用本地缓存(如apcu)+ TTL(如 1 秒),再辅以 Redis 的过期键监听或定时刷新 - 注意连接池干扰:若复用
AsyncTcpConnection,确保熔断触发时也主动关闭对应连接,防止连接池持续向已熔断服务发包
最容易被忽略的点:半开状态下的请求节流与结果反馈
进入 Half-Open 后,不是“放一个请求试试”,而是要控制试探请求的频率和比例。否则在高并发下,多个 Worker 同时发起探测,等同于全量压测。
建议做法:
- 用 Redis 的
INCR+EXPIRE实现分布式令牌桶,限制每秒最多 2 个探测请求 - 探测成功后,必须重置所有统计(失败计数、时间窗口起始时间),而不仅是切换状态
- 记录每次状态变更日志,包含
from、to、reason(如 “failure rate=0.82 > threshold=0.5”),方便排查是网络抖动还是真实故障 - 不要在
onClose或onError中触发熔断逻辑 —— 它们不保证执行时机,且无法捕获超时类故障











