Go熔断器默认不自动进入半开状态,必须显式配置ReadyToTrip函数返回true且Timeout到期才能触发;若只设MaxRequests和Timeout而未实现ReadyToTrip(默认始终false),则永远卡在open状态。

Go 熔断器默认不自动进入半开状态,必须显式配置 ReadyToTrip 函数 + Timeout 才能触发探测;只设 MaxRequests: 1 和 Timeout 而不提供 ReadyToTrip,熔断器会永远卡在 open。
为什么你的熔断器从不进 half-open?
常见错误是以为只要配了 Timeout 就会“自动轮询恢复”,但 github.com/sony/gobreaker 等主流实现中,Timeout 只是 open 状态的持续时间,是否切换到 half-open 完全取决于 ReadyToTrip 函数返回 true。这个函数默认是空实现(始终返回 false),所以你得自己写逻辑。
-
ReadyToTrip必须检查统计窗口内的失败率或失败次数,比如 “最近 10 次请求失败 ≥ 7 次” - 不能只依赖时间,否则可能在下游还没恢复时就放行探测请求
- 如果用
resilience-go,它把ReadyToTrip封装为OnStateChange回调里的手动Reset(),逻辑更显式但更易出错 - 漏掉
OnStateChange监听或忘记在 open 后启动探测 goroutine,也会导致零探测
半开状态下只允许一个请求通过,怎么避免卡死?
gobreaker 的 Allow() 在 half-open 下内部用原子计数器控制:仅第一个调用返回 nil,后续立刻返回 ErrTooManyRequests,直到该请求完成(MarkSuccess 或 MarkFailure 被调用)。这意味着:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 探测请求必须带
context.WithTimeout,超时时间应明显短于Timeout(例如Timeout: 60 * time.Second,探测用5 * time.Second) - 不能把未设 timeout 的 HTTP 请求直接丢进去——一旦 hang 住,整个半开窗口冻结,所有新请求排队等那个没响应的探测
- 业务代码里必须确保无论成功失败、panic 还是提前 return,都要调用
MarkSuccess()或MarkFailure();漏掉任一路径,状态就永久卡在 half-open - 不要在
http.Client上复用连接池或重试策略,探测请求应走独立轻量 client(如只连/health)
想定期 ping 探测服务?别走熔断器主流程
标准熔断器设计是响应式(request-driven),不是主动式(timer-driven)。你在定时器里反复调 cb.Allow() 不仅不会触发真实探测,还会污染统计指标、干扰状态机,甚至让业务请求被误拒。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:监听
OnStateChange,当状态变为StateOpen时,起一个独立 goroutine +time.AfterFunc,用专用 health client 发起探测 - 探测成功(HTTP 200 + latency cb.Reset() 强制切回 closed;失败则重置定时器
- 健康检查 endpoint 必须和主业务分离:不同 path(如
/health)、不同 client、不同超时、不共享中间件(如 auth、trace) - 避免用
sentinel-go做这事——它的熔断规则必须靠circuitbreaker.LoadRules显式加载,且不支持外部 reset,强行调用会绕过规则校验
真正难的不是写那几行 ReadyToTrip,而是确保探测请求本身足够轻、够快、够独立,并且失败路径全部覆盖。线上最常出问题的,是探测用了和主链路一样的 http.Client,结果下游数据库连不上,/health 也一起超时,探测永远失败,熔断器再也没机会恢复。

















