应直接使用 github.com/sony/gobreaker,禁用手写状态机和已归档的 hystrix-go;必须按下游服务维度创建独立实例并封装 RoundTripper,在 RoundTrip 中调用 cb.Execute 透传 req 和 ctx;Timeout 设为下游 P95 耗时的 2–3 倍,ReadyToTrip 需过滤错误类型,仅将超时类错误计入失败,降级逻辑须纯内存无副作用。

直接用 github.com/sony/gobreaker,别手写状态机、也别碰已归档的 hystrix-go——它轻量、线程安全、原生支持 context.Context,且被 go-zero、kratos、grpc-go 官方示例广泛采用。自己实现大概率在并发激增时漏统计失败、半开态卡死,或把 404 当故障熔断。
为什么必须按下游服务维度创建独立 gobreaker.CircuitBreaker 实例
熔断器不是全局开关,而是每个下游服务专属的“保险丝”。共用一个实例会导致 A 服务故障把 B 服务也拖垮。
- 每个 HTTP 客户端、gRPC stub、数据库连接池都应配独立
cb实例 - 命名带上服务标识,比如
Name: "payment-service-call",方便监控和排查 - 低频服务(如管理后台接口)要调小
RequestVolumeThreshold,否则永远达不到统计窗口,熔断器形同虚设
HTTP 客户端必须封装 RoundTripper,不能在外层套 cb.Execute
错误做法是 cb.Execute(func() { http.DefaultClient.Get(...) })——这会破坏连接复用、让超时控制失效、导致 ctx 传不进底层 Transport。
- 正确路径:实现自定义
http.RoundTripper,在RoundTrip方法里调用cb.Execute - 务必透传原始
*http.Request和req.Context(),否则context.DeadlineExceeded不会被捕获 - 闭包陷阱高发:循环中直接捕获
req变量 → 所有请求共用同一地址 → panic:http: Request.Write on Body closed - 安全写法:显式传参,例如
func(req *http.Request) func() (interface{}, error) { return func() { return client.Do(req) } }
ReadyToTrip 和 Timeout 怎么配才不翻车
90% 的“熔断不生效”或“一直不恢复”,问题就出在这俩参数上。默认值只适合本地调试,上线必调。
立即学习“go语言免费学习笔记(深入)”;
-
Timeout(熔断持续时间):设为下游 P95 耗时的 2–3 倍;默认60s太长,下游已恢复,你的服务还在拒流;生产建议10s–30s -
ReadyToTrip(触发条件):必须过滤错误类型,只将context.DeadlineExceeded、net/http.ErrTimeout、io.EOF视为失败;404、401、sql.ErrNoRows必须放过 -
MaxRequests(半开试探请求数):设3–5较安全;设1恢复慢,设100可能压垮刚恢复的下游
fallback 函数必须纯内存、无副作用
gobreaker 的 Execute 第三个参数才是降级入口,不是熔断器自带能力——它只在 gobreaker.ErrOpenState 或执行失败时触发。
- 返回兜底 JSON、静态文案、或带
staleheader 的上一次成功响应即可 - 不能查 Redis、不能调配置中心、不能嵌套外部 HTTP 请求——那等于绕过熔断保护
- 函数签名必须匹配主函数:
func() (interface{}, error),内部不要panic - 务必配合
context.WithTimeout使用——熔断只管开关,超时得你自己控,否则fallback也可能卡住
真正难的不是初始化一个 gobreaker.CircuitBreaker,而是把 RoundTrip 包装对、把错误分类清、把降级做薄——这三处任一出错,熔断就从保护变成阻塞。


















