应使用 github.com/sony/gobreaker,它轻量、线程安全、支持 context.Context,且经 go-zero、kratos 等框架验证;勿在外层用 cb.Execute 包裹 http.Client.Get,须实现自定义 RoundTripper,在 RoundTrip 中调用 cb.Execute 并透传 req 和 req.Context();Timeout 设为下游 P95 耗时的 2–3 倍,ReadyToTrip 需按错误类型过滤,仅将超时类错误计入失败。

直接用 github.com/sony/gobreaker,别手写、别用 hystrix-go。它轻量、线程安全、支持 context.Context,且被 go-zero、kratos 等主流框架验证过生产可用性。
为什么不能在外层套 cb.Execute 包 http.Client.Get
这种写法看似简单,实则破坏 HTTP 客户端核心机制:
- 超时控制失效:外部
context.WithTimeout无法透传到底层net.Conn,连接可能卡死 - 连接池复用被破坏:每次调用都绕过
http.Transport的连接管理,导致 fd 耗尽 -
cb.Execute只能捕获闭包 panic 或 error,但无法感知响应体状态码(如 503)
正确做法是实现自定义 http.RoundTripper,在 RoundTrip(*http.Request) 内部调用 cb.Execute,并把原始 req 和 req.Context() 透传给底层 Transport.RoundTrip。
ReadyToTrip 和 Timeout 怎么配才不误熔断
这两个参数默认值只适合本地调试,线上必须按真实流量调优:
立即学习“go语言免费学习笔记(深入)”;
-
Timeout(熔断持续时间):设为下游 P95 耗时的 2–3 倍;默认 60s 太长,下游已恢复,你的服务还在拒流 -
ReadyToTrip(触发条件):不要依赖默认的“100 次失败率 > 60%”;应过滤错误类型——只将context.DeadlineExceeded、net/http.ErrTimeout算失败,sql.ErrNoRows或 HTTP 404 必须放过 - 低频服务(QPS RequestVolumeThreshold,否则永远达不到统计窗口,熔断器形同虚设
cb.Execute 闭包里怎么传参才不 panic
这是线上最隐蔽的坑:循环中直接闭包捕获 req 变量,会导致所有请求共用同一内存地址,最终触发 http: Request.Write on Body closed panic。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
错误写法:
for _, url := range urls {<br> req, _ := http.NewRequest("GET", url, nil)<br> cb.Execute(func() (interface{}, error) {<br> return http.DefaultClient.Do(req) // 所有闭包共享同一个 req 指针<br> })<br>}
正确写法:
for _, url := range urls {<br> req, _ := http.NewRequest("GET", url, nil)<br> cb.Execute(func(req *http.Request) func() (interface{}, error) {<br> return func() (interface{}, error) {<br> return http.DefaultClient.Do(req)<br> }<br> }(req))<br>}
关键点:用立即执行函数把 req 显式传入闭包作用域,避免变量捕获污染。
降级逻辑为什么不能再去查 Redis 或调配置中心
cb.Execute 返回 gobreaker.ErrOpenState 时,你必须立刻返回一个纯内存、无副作用的结果:
- 再查 Redis → 可能引发级联雪崩,且违背熔断“快速失败”原则
- 再调配置中心 → 引入新依赖,失去保护意义
- 降级应带业务语义:返回上一次成功响应(加
staleheader 标识)、静态兜底 JSON、或空结构体(需前端兼容)
真正难的不是写熔断逻辑,而是识别哪些错误该进统计、哪些该放过,以及降级结果是否真能撑住用户侧体验——这需要结合接口 SLA 和业务容忍度反复校准。

















