service层必须显式封装降级逻辑,框架不自动fallback;需手动判断网络类错误、统一用errors.is检查超时、降级函数纯内存无副作用、熔断配置单位为毫秒、开关需对接配置中心watch。

service 层必须显式封装降级逻辑,不能依赖框架自动兜底
Go 框架(如 go-zero、kratos、kit)本身不提供“调用失败就自动 fallback”的能力。服务发现返回 err != nil、RPC 调用返回 status.Code() == codes.Unavailable、HTTP 客户端返回 net.OpError——这些错误都得你在 service 方法里手动判断并跳转,框架不会帮你执行 getDefaultUser()。
- handler 层只做参数校验和响应包装,降级逻辑塞进去会导致复用困难、测试难覆盖、超时/熔断策略无法统一
- 每个关键依赖调用后必须立刻检查错误,别堆到函数末尾统一处理;否则上游已 cancel,你却还在等下游返回
- 降级函数签名必须严格匹配主逻辑,例如
func GetUser(ctx context.Context, id string) (*User, error)的降级也得返回(*User, error) - 禁止在降级里发新请求、查 DB、调
time.Now()或rand.Intn()——它必须是纯内存计算或本地缓存读取
errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) 是唯一可靠超时判断
很多降级没触发,不是逻辑错了,而是判断条件写错。只写 err == context.DeadlineExceeded 会漏掉 context.Canceled(比如前端关闭页面、gRPC 流中断),导致直接返回 500。
- gRPC 错误要用
status.FromError(err).Code()判断语义,但底层仍是 context 错误,仍需用errors.Is检查 - 禁止用
strings.Contains(err.Error(), "timeout"):HTTP/HTTP2/gRPC/数据库驱动返回的错误文本不一致,“i/o timeout”、“context deadline exceeded”、“transport: context deadline exceeded”都可能出现 - 业务类错误如
sql.ErrNoRows或 HTTP404不该进降级分支,它们是正常态,不是故障信号 - 网络类错误(
net.OpError、syscall.ECONNREFUSED)才值得走 fallback,建议用errors.Is(err, syscall.ECONNREFUSED)精确匹配
gobreaker.Settings.Timeout 单位是毫秒,填错会导致降级根本没机会执行
gobreaker.Settings.Timeout 是熔断器内部等待主函数返回的上限,单位是 time.Millisecond。设成 500 是 500ms,不是 5 秒——填错会让请求在熔断器里直接超时退出,连降级函数的影子都见不到。
- 这个值应略大于你的主调用 P99 延迟,比如主调用 P99 是 300ms,这里设 400~500
-
gobreaker.Settings.ReadyToTrip默认统计最近 100 次调用,高并发下可能几毫秒内就触发熔断,导致部分请求走 fallback、部分被拒绝,行为不一致 - fallback 函数必须无副作用、类型匹配、不 panic——它被
cb.Execute直接调用,一旦出错会暴露给上层 - 别用全局变量存 fallback 实例;每次新建开销极小,且避免并发修改风险
NeedFallback 函数要融合人工开关、熔断状态与配置中心 watch
服务发现不可用 ≠ 必须降级。比如运维灰度升级时所有实例临时下线,这时靠熔断器统计失败次数来触发降级太慢,应该由配置中心强制开启开关。
- 实现统一判断函数:
func NeedFallback(serviceName string) bool,按优先级检查:config.GetBool("downgrade." + serviceName)→breaker.State() == gobreaker.StateOpen→ false - 开关必须可被单元测试覆盖,且能响应配置中心(etcd/Consul/Nacos)的 watch 事件实时更新
- 日志和打点要放在主流程失败后统一处理,降级分支里禁止写日志(避免日志风暴)
- 本地 LRU 缓存要配合 singleflight 防雪崩,但注意
singleflight.Do默认广播错误结果,需外层校验err == nil再决定是否写入缓存
ctx.Done() 没被正确捕获,一次 gobreaker.Settings.Timeout 单位填错,或者开关没接配置中心 watch,都会让降级形同虚设。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











