必须为 net/http 调用第三方 API 配置熔断,推荐使用 sony/gobreaker 全局单例、配合超时 HTTP Client 和定制 RoundTripper,精细分类错误(如 5xx 熔断、429 退避、4xx 不重试),避免误判雪崩。

Go 里直接用 net/http 调第三方 API 是最简路径,但没熔断就等于把整个服务的稳定性押在对方身上——一旦依赖接口抖动或超时,你的调用线程/协程会堆积、资源耗尽、雪崩风险立刻上升。必须加熔断,且不能靠“手动计数+全局锁”这种土法炼钢。
用 circuitbreaker 包做基础封装,别自己写状态机
自己实现熔断器要处理半开状态、滑动窗口、重置逻辑、并发安全,容易出错。社区成熟的 sony/gobreaker 已被大量线上项目验证,它基于状态机(Closed/Open/Half-Open)和失败率阈值,还支持自定义错误判断和回调。
- 安装:
go get github.com/sony/gobreaker - 初始化时设置
MaxRequests(半开状态下允许试探的请求数)、Interval(Open 状态持续多久后自动进入 Half-Open)、Timeout(失败计数的滑动窗口时间) - 不要把
gobreaker.NewCircuitBreaker放在 handler 内反复创建,它应是全局单例,按依赖域名或业务域区分实例
HTTP Client 必须配合超时与重试策略
熔断只管“要不要发”,不管“发了怎么收”。如果底层 HTTP 请求不设限,一次卡死就会拖垮整个熔断器的状态判断——比如 http.DefaultClient 的默认 timeout 是 0(无限等待),这会让 gobreaker 在 Open 状态下仍因阻塞而无法及时响应新请求。
- 构造专用 client:
http.Client{Timeout: 5 * time.Second},超时必须显式设,且建议 ≤ 熔断器的Timeout参数 - 重试只应在
Closed状态下进行,且最多 1 次;Half-Open下不应重试,否则试探请求可能失败并重置状态 - 避免对 4xx 错误重试(如
400 Bad Request),它们通常不是临时故障;重点捕获context.DeadlineExceeded、net.ErrTimeout、net.OpError这类网络层错误
把熔断逻辑嵌进 http.RoundTripper,统一拦截所有 outbound 请求
如果每个 API 调用都手动 wrap cb.Execute,代码重复高、易漏、难维护。更合理的方式是定制 http.RoundTripper,让熔断成为 transport 层能力。
- 实现一个
circuitBreakerTransport结构体,内嵌http.RoundTripper和*gobreaker.CircuitBreaker - 在
RoundTrip方法里调用cb.Execute,传入原始http.RoundTrip函数作为执行体 - 注意:
cb.Execute的第一个参数是func() (interface{}, error),需把*http.Request闭包进去,并在内部完成rt.RoundTrip(req) - 这样下游只需配置 client:
&http.Client{Transport: &circuitBreakerTransport{...}},所有请求自动受控
错误分类必须精细,否则熔断会误判
gobreaker 默认把所有 error 都当失败,但第三方 API 的 503(Service Unavailable)和 429(Rate Limited)语义完全不同:前者是对方不可用,该熔断;后者是你调太猛,该降频而非熔断。粗暴统一会导致本可恢复的服务被长期隔离。
- 通过
gobreaker.Settings.OnStateChange注册回调,记录状态切换日志(如 “moved to Open due to 5xx”)便于事后分析 - 自定义
gobreaker.Settings.ReadyToTrip函数,只对特定错误码或网络错误返回 true;例如检查err是否为*url.Error或响应 status 是否匹配5xx - 对 429 做单独退避(如解析
Retry-Afterheader),并在 client 层直接 sleep,不走熔断流程
真正难的不是加熔断,而是厘清哪些错误该触发、哪些该绕过、哪些该重试。很多团队上线后发现熔断器频繁跳变,最后查出来是把日志上报失败也计入了失败率——第三方日志服务挂了,不该影响核心交易链路。这类边界得在设计 transport 层时就切干净。


















