gobreaker.Execute的fallback仅在ErrOpen或panic时触发,不处理超时、HTTP 5xx、连接拒绝等错误;必须显式判断errors.Is(err, gobreaker.ErrOpen)等并手动调用签名一致的零延迟降级函数。

gobreaker.Execute 的 fallback 参数只响应 ErrOpen 和 panic,不处理超时、5xx、连接拒绝等错误——必须手动判断并调用降级函数。
gobreaker.Execute 的 fallback 什么时候才触发
很多人以为把降级逻辑塞进 cb.Execute 的第三个参数(fallback 函数),就能覆盖所有失败场景。实际不是:
-
gobreaker.ErrOpen:熔断器处于 Open 状态时触发 - panic:闭包内发生 panic 时触发
-
context.DeadlineExceeded、context.Canceled、HTTP 503、net.OpError等——统统不进 fallback,原样返回给上层
也就是说,下游服务返回 500 或请求超时,cb.Execute 直接把 error 吐出来,根本不会执行你写的 fallback 函数。
降级逻辑必须显式判断错误类型再调用
正确写法是:先执行 cb.Execute,再对 err 做分类处理,符合条件就手动调降级函数:
立即学习“go语言免费学习笔记(深入)”;
- 用
errors.Is(err, gobreaker.ErrOpen)判断是否熔断 - 用
errors.Is(err, context.DeadlineExceeded)或errors.Is(err, context.Canceled)捕获超时/取消 - 对 HTTP 响应码主动转 error(如
resp.StatusCode >= 500),再统一走降级分支 - 降级函数签名必须和主函数完全一致,比如主函数返回
(*User, error),降级函数也得是func() (*User, error)
示例片段:
res, err := cb.Execute(func() (interface{}, error) {
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode >= 500 {
return nil, fmt.Errorf("http %d", resp.StatusCode)
}
return resp, nil
})
if errors.Is(err, gobreaker.ErrOpen) || errors.Is(err, context.DeadlineExceeded) {
return fallbackUser() // 显式调用
}
if err != nil {
return fallbackUser()
}
降级函数不能有 IO、不能阻塞、不能依赖外部服务
降级不是“换条路重试”,而是“立刻给结果”。常见踩坑点:
- 在
fallbackUser()里调db.QueryRow或发新 HTTP 请求——违背零延迟原则,拖慢主链路 - 返回
&User{}这种全零值结构体——前端序列化后字段为空,可能被误判为“真实空数据” - 写成
func() *User(只返回指针)——签名不匹配,cb.Execute编译报错或运行 panic - 降级返回
nil, nil——上层可能 panic,建议至少返回一个有效指针 +nilerror
熔断器配置容易忽略的关键参数
gobreaker.Settings 里几个参数直接影响降级能否及时生效:
-
MaxRequests:半开状态允许试探的请求数,设太小(如 1)会导致刚恢复就拒掉所有请求 -
Timeout:熔断后保持 Open 的最短时间,不是 HTTP 超时,建议设 10–15s,别设成 60s -
Interval:滑动窗口长度,统计失败率的时间范围,高频服务建议 30s~2m,太短会把瞬时抖动当故障 -
ReadyToTrip:判定函数,别只看ConsecutiveFailures,建议结合失败率(float64(counts.TotalFailures)/float64(counts.Requests))
真正难的不是加一行 cb.Execute,而是让降级在超时、5xx、熔断这三类典型故障下都稳定触发——它们触发时机不同、错误类型不同、处理路径也不同,得分开兜住。


















