Kratos 框架不内置服务降级开关,降级必须在 circuitbreaker.Client/Server 的 fallback 函数中显式实现,且该函数须为纯内存、无副作用、不阻塞、不 panic,并严格匹配主调用签名。

Kratos 框架本身不内置“服务降级开关”或全局降级中心,它的降级能力必须由开发者在 circuitbreaker.Client 或 circuitbreaker.Server 的 fallback 函数中显式实现——而且这个函数不能有外部依赖、不能阻塞、不能 panic。
kratos/circuitbreaker 的 fallback 必须是纯内存无副作用函数
很多人误以为 Kratos 的熔断中间件自带降级逻辑,其实它只负责状态切换和拦截;真正返回兜底数据的代码,得你自己写进 fallback 参数里。
-
fallback函数签名必须匹配主调用:比如主函数返回(User, error),fallback 也得返回(User, error) - 禁止在 fallback 里调用数据库、Redis、HTTP 客户端、gRPC stub —— 否则降级反而成新故障点
- 推荐做法:返回预设的本地变量(如
defaultUser)、内存缓存快照(如userCache.GetStale("1001"))、或空结构体 + 友好错误(nil, errors.New("user service degraded")) - 如果 fallback 抛 panic,kratos 会捕获并转为
500 Internal Server Error,但此时指标已丢失,无法区分是真实失败还是降级异常
为什么不能把 fallback 写在 middleware 层
Kratos 的中间件链(如 ratelimit.Server、circuitbreaker.Server)只作用于请求生命周期,而服务降级本质是「对某个下游依赖的替代响应」,必须和具体调用强绑定。
- 在 HTTP handler 里做 fallback:容易混入业务逻辑,且无法复用到 gRPC client 场景
- 在 gRPC stub 调用外层包 fallback:可行,但绕过了 kratos 的
circuitbreaker.Client统一管控 - 正确姿势:只在
circuitbreaker.Client的fallback里写降级逻辑,且确保该 client 实例专用于某一个下游服务(如userClient) - 错误示例:
breaker := sre.NewBreaker(...); grpc.Dial(..., circuitbreaker.Client(circuitbreaker.WithCircuitBreaker(func() { return breaker }))—— 这里breaker是共享实例,不同服务共用会导致降级策略互相污染
fallback 触发时机与常见误判
不是所有错误都会触发 fallback。Kratos 的 sre.Breaker 只在两种情况下执行 fallback:一是状态为 Open(熔断开启),二是主函数执行时 panic 或返回非 nil error 且未被 breaker 统计为失败(比如超时未走完统计窗口)。
- HTTP 场景下,
resp.StatusCode >= 500必须主动转 error,否则 breaker 不计入失败,也就不会进入 Open 状态,fallback 永远不触发 - gRPC 场景下,
status.Code(err) == codes.Unavailable或codes.Internal才应作为失败依据,别直接用err != nil—— 很多临时错误(如codes.DeadlineExceeded)不该拉高错误率 - Timeout 和 fallback 不是一回事:
context.WithTimeout控制单次调用上限,fallback是熔断后或调用失败时的替代动作;两者必须同时存在,缺一不可 - 测试 fallback 是否生效:手动把下游服务停掉 → 发起请求 → 看是否返回预期兜底值 + Prometheus 中
circuit_breaker_fallbacks_total计数上涨
最容易被忽略的是 fallback 的可观测性:Kratos 不自动暴露 fallback 执行次数,你得自己在 fallback 函数里打日志或上报指标;否则线上降级了却没人知道,等于白配。











