必须全局复用同一个gobreaker.circuitbreaker实例,否则每次new都会创建独立计数器导致熔断失效;每个下游服务需独占线程安全实例;http熔断须通过自定义roundtripper实现,降级须显式判断gobreaker.erropen并加超时控制。

Go 语言没有内置 circuit breaker,必须用 github.com/sony/gobreaker,别手写、别用已归档的 hystrix-go —— 否则并发下计数错、半开失控、超时不中断,线上直接雪崩。
为什么不能每次调用都 new 一个 gobreaker.CircuitBreaker
反复调用 gobreaker.NewCircuitBreaker,等于给每次请求配独立计数器:失败不累积、熔断不触发、整个机制失效。
- 正确做法是初始化一次,存为包级变量或注入结构体(如
type UserService struct { cb *gobreaker.CircuitBreaker }) - 每个下游服务(
user-svc、order-svc)必须独占一个实例,共用会导致故障互相污染 -
gobreaker.CircuitBreaker本身线程安全,但运行时改ReadyToTrip需同步,不建议动态频繁调
HTTP 熔断必须封装 RoundTripper,不是在外层套 cb.Execute
在 http.Client.Do 外硬包 cb.Execute,会破坏连接复用、丢失 context 透传、超时控制失效,且无法统一拦截所有请求。
- 正确方式是实现自定义
http.RoundTripper,在RoundTrip方法里调cb.Execute,并透传原始*http.Request和req.Context() -
Open状态下必须返回合法的*http.Response(哪怕Body = io.NopCloser(bytes.NewReader(nil))),否则上层resp.Body.Read会 panic - 原始
*http.Request要显式复制:req.Clone(req.Context()),避免修改影响后续中间件
gobreaker.ErrOpen 是唯一可靠的熔断信号,降级必须显式判断
cb.Execute 不自动触发 fallback;它只在 Open 状态或主函数 panic 时返回 gobreaker.ErrOpen,其他 error(如超时、404、503)只是业务失败,不表示熔断。
- 降级逻辑必须独立分支:
if errors.Is(err, gobreaker.ErrOpen) { return getFallbackUser() } - 降级函数必须纯内存、无副作用:不能查 Redis、不能发 HTTP、不能调配置中心;返回静态值、缓存快照或带
staleheader 的上一次成功响应即可 - 降级本身也要加超时控制,避免拖慢整条链路;建议用
context.WithTimeout包一层
Timeout 和 ReadyToTrip 必须按 P95 耗时与错误类型调优
默认值(Timeout=60s、ReadyToTrip 默认统计所有失败)只适合本地调试,线上不调等于没开熔断。
-
Timeout设为下游 P95 耗时的 2–3 倍(比如 P95 是 800ms,设为2 * time.Second);设太长会导致下游已恢复,你的服务还在拒流 -
ReadyToTrip必须过滤错误类型:只将context.DeadlineExceeded、net/http.ErrTimeout等超时类错误计入失败;404、401、sql.ErrNoRows必须放过 - 低频服务要调小
RequestVolumeThreshold,否则永远达不到统计窗口,熔断器形同虚设
真正难的不是创建一个 gobreaker.CircuitBreaker,而是 RoundTripper 封装对、错误分类清、降级做薄——这三处任一出错,熔断就从保护变成阻塞。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











