必须精准包裹真实网络调用(如client.Do(req)),而非整个client或handler;否则失败不统计、熔断失效;fallback仅在Open或panic时触发,需手动判断gobreaker.ErrOpen并显式调用,且签名须与主函数严格一致。

只对真实网络调用包裹 gobreaker.Execute
不是把整个 handler 或 client 实例塞进去,而是精准包住 client.Do(req)、stub.GetUser(ctx, req) 这类真正发请求的语句。否则失败计数永远为 0,熔断器形同虚设。
常见错误是把 http.Client 或 grpc.ClientConn 传给 Execute,但它根本不执行任何网络动作;或者在 Execute 内部做日志、重试、metric 上报——这些都会污染失败判定逻辑。
- 返回值必须是
(interface{}, error):哪怕结果是nil,也要显式返回,否则成功不被统计 -
context.DeadlineExceeded、grpc codes.Unavailable、HTTP 5xx 都得转成error才计入失败 - 每个下游服务(如
payment-api、user-svc)必须配独立gobreaker.CircuitBreaker实例,命名带服务标识,避免故障蔓延
降级逻辑必须手动触发,不能依赖 fallback 自动执行
gobreaker.Execute 的第三个参数 fallback 只在两种情况下触发:熔断器处于 Open 状态(返回 gobreaker.ErrOpen),或主函数 panic。它不会捕获超时、503、context canceled 等常规错误。
所以真正的降级路径是:先检查 errors.Is(err, gobreaker.ErrOpen),再显式调用你的降级函数;同时也要覆盖超时、网络错误等场景,统一走 fallback 流程。
立即学习“go语言免费学习笔记(深入)”;
-
fallback函数签名必须和主函数完全一致,比如主函数是func() (*User, error),fallback 也得是这个类型 - 降级函数内部禁止访问 DB、调其他服务、起 goroutine;推荐从
sync.Map或本地 LRU cache 读缓存,或用DefaultUser()构造静态响应 - 返回
&User{}是危险的——序列化后是{"name":"","age":0},前端可能当成真实空数据;应显式初始化字段
HTTP 中间件里嵌入降级,必须在写响应前判断状态
降级不是“出错了才执行”,而是要和熔断状态、上下文联动。关键插入点是 w.WriteHeader() 和 w.Write() 之前,否则可能已写部分响应头再中断,客户端收不到完整 fallback 内容。
典型结构是:先调用业务函数 → 检查是否熔断/超时/失败 → 若需降级,清空已写 header(用 w.Header().Del())、设置新 status code 和 body → 返回。
- 别用全局变量存熔断器实例,否则多个路由共享状态,互相干扰
- 中间件签名建议为
func(http.HandlerFunc) http.HandlerFunc,通过闭包注入对应服务的cb实例 - 如果模块同时调多个第三方 API,每个 API 应有独立中间件 + 独立熔断器,不要复用
结合 context.WithTimeout 控制单次调用超时,别混淆 Timeout 参数
gobreaker.Settings.Timeout 是熔断开启后持续多久才尝试半开,不是单次调用超时时间;单次超时必须由 context.WithTimeout 控制。
例如下游 API 要求 800ms 内返回,就该用 ctx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond),然后把 ctx 传给 client.Do(req.WithContext(ctx))。
- 超时错误必须转成
error并返回,否则不计入失败统计 -
Interval是滑动窗口重置周期,设太短会导致抖动误判,设太长会延迟恢复;建议按 P95 RT 的 3–5 倍设(如 P95 是 300ms,Interval 设 2s) - 别把
ReadyToTrip写成固定阈值,比如counts.ConsecutiveFailures > 5;更稳的做法是结合错误率 + 时间窗口,比如counts.TotalFailures / counts.TotalRequests > 0.5 && counts.TotalRequests >= 10


















