应直接使用 github.com/sony/gobreaker,禁止在外层包装 http.Get 或 cb.Execute;必须实现自定义 RoundTripper,在 RoundTrip 中调用 cb.Execute 并透传 *http.Request 和 req.Context(),同时按 P95 耗时设 Timeout、按错误类型过滤 ReadyToTrip。

直接用 gobreaker,别包装 http.Get 或外层套 cb.Execute —— 那样会破坏超时、连接复用和上下文透传,熔断器反而变成故障放大器。
HTTP 熔断必须封装 RoundTripper,不是包装 Do
你不能写 cb.Execute(func() { http.DefaultClient.Get(url) })。这种写法会让:
-
http.Client.Timeout和cb.Timeout冲突,底层 Transport 拿不到 context - 每次调用都新建或绕过连接池,
Keep-Alive失效,连接泄漏风险陡增 -
net/url.Error、404、401全被当失败计入,触发误熔断
正确做法是实现一个自定义 http.RoundTripper,在 RoundTrip(*http.Request) 里调用 cb.Execute,并确保透传原始 req.Context() 和 *http.Request。返回的 *http.Response 必须带非 nil Body(哪怕用 io.NopCloser(bytes.NewReader(nil)) 填充),否则上层 resp.Body.Close() 会 panic。
ReadyToTrip 和 Timeout 必须按 P95 耗时与错误类型调优
90% 的“熔断不生效”或“一直不恢复”,都出在这两个参数上。默认值只适合本地调试:
立即学习“go语言免费学习笔记(深入)”;
-
Timeout:设为下游依赖 P95 耗时的 2–3 倍;默认60 * time.Second太长,下游已恢复,你的服务还在拒流 -
ReadyToTrip:必须显式过滤错误类型,例如只将context.DeadlineExceeded、net/http.ErrTimeout、503视为失败;404、401、sql.ErrNoRows必须放过 -
RequestVolumeThreshold:低频服务(如管理后台)要调小(比如设为 5),否则永远达不到统计窗口,熔断器形同虚设
闭包传参和降级逻辑必须无副作用
循环中闭包捕获 req 是线上最隐蔽的 panic 来源:
- 错误写法:
for _, url := range urls { req, _ := http.NewRequest(...) cb.Execute(func() { client.Do(req) }) }→ 所有闭包共享同一req地址 →http: Request.Write on Body closed - 正确写法:
cb.Execute(func(req *http.Request) func() (interface{}, error) { return func() { return client.Do(req) } }(req)) - 降级逻辑不能查 Redis、不能调配置中心、不能发新 HTTP 请求;必须纯内存、无 IO、无锁、无 goroutine 泄漏;返回兜底 JSON、静态文案,或带
staleheader 的上一次成功响应即可 - 降级必须和
cb.Execute解耦:先执行,捕获gobreaker.ErrOpen,再走独立 fallback 分支;别把 fallback 塞进 Execute 闭包里
真正难的不是初始化一个 gobreaker.CircuitBreaker,而是 RoundTripper 包装对、错误分类清、降级做薄——这三处任一出错,熔断就从保护变成阻塞。

















