gobreaker.execute的fallback仅在erropen或panic时触发,不处理超时、http 5xx等错误;须显式判断errors.is(err, gobreaker.erropen)等并单独调用签名一致的零延迟降级函数。

gobreaker.Execute 的 fallback 参数根本不会处理超时和 HTTP 错误
很多人把降级逻辑塞进 cb.Execute 的第三个参数(fallback 函数),就以为万事大吉。结果线上一出问题,503、超时、连接拒绝全都不走降级——因为 gobreaker.ErrOpen 和 panic 才触发 fallback,其他错误原样返回。
常见错误现象:
-
context.DeadlineExceeded被忽略,主函数继续执行直到真正失败 - HTTP 返回
503 Service Unavailable,cb.Execute直接返回该 error,不进 fallback -
net.OpError(如 dial timeout)被计入失败计数,但降级函数完全没调用
正确做法是:先调 cb.Execute,再显式判断错误类型:
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) {
return fallbackUser()
}
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
return fallbackUser()
}
if err != nil {
// 其他错误(如 5xx)也可按需降级
return fallbackUser()
}
降级函数签名必须和主函数严格一致
主函数返回 func() (*User, error),降级函数也得是这个签名。返回 *User 单独一个值,或只返回 error,都会导致编译失败或 panic。
容易踩的坑:
- 写成
func() *User—— 类型不匹配,cb.Execute拒绝执行 - 在降级里调
db.QueryRow或发新 HTTP 请求 —— 违反“零延迟”原则,拖慢主链路 - 用
&User{}当默认值 —— 序列化后字段全为零值,前端可能误判为真实空数据
推荐写法:
func fallbackUser() (*User, error) {
// 从 sync.Map 或本地 LRU cache 读旧数据
if u, ok := userCache.Load(123); ok {
return u.(*User), nil
}
// 否则构造明确语义的默认值
return &User{
ID: 123,
Name: "用户信息暂不可用",
Age: 0,
}, nil
}
每个下游服务必须配独立 gobreaker 实例
复用同一个 gobreaker.CircuitBreaker 实例去包 user-service 和 order-service,等于把两个服务的故障面绑死:A 挂了,B 的请求也被拦住。
配置关键点:
-
MaxRequests设为 6~10:太小(如 1)易误熔,太大响应迟钝 -
Timeout是熔断器等待半开探测的时间,不是请求超时 —— 建议设 15s -
ReadyToTrip用ConsecutiveFailures > 5比单纯看失败率更稳 -
Interval控制滑动窗口重置周期,设 30s~2m,避免瞬时抖动误判
HTTP 超时必须由 context.WithTimeout 控制,且应比 gobreaker.Timeout 短 20%~30%。例如 HTTP 超时设 800ms,breaker Timeout 设 1.2s。
降级不是兜底,是确定性快路径
真正的降级函数里不能有不确定操作:查 Redis、起 goroutine、调另一个 cb.Execute、甚至同步写日志都可能拖慢响应。它必须是纯内存、无 I/O、无锁、不 panic 的确定性路径。
典型安全做法:
- 缓存旧数据:从
sync.Map或本地 LRU 读,带明确过期时间 - 静态构造:用
DefaultUser()这类函数返回带业务语义的默认值 - 轻量计算:服务不可用时退化为按 ID 取模排序,而非调远程模型
最常被忽略的一点:降级逻辑必须和熔断开关动态联动。比如你通过配置中心关闭了某个降级开关,代码里却硬编码了 fallback 调用 —— 这种“假降级”上线后根本不起作用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











