Kratos 不提供开箱即用的雪崩防护,需业务方在 service 或 middleware 层显式接入熔断、限流、降级等组件,因其设计哲学是“工具箱”而非“黑盒框架”,不预设调用链路与 SLA 要求。

Kratos 框架本身不提供开箱即用的雪崩防护能力,所有熔断、限流、降级、隔离逻辑必须由业务方在 service 层或 middleware 层显式接入 —— 它只暴露 aegis(限流/熔断算法库)、middleware/ratelimit、middleware/circuitbreaker 等可插拔组件,但默认不启用。
为什么 Kratos 不自动熔断?
Kratos 的设计哲学是「工具箱」而非「黑盒框架」:它不假设你的服务调用链路、SLA 要求、错误容忍阈值。比如 aegis.CircuitBreaker 需要你手动包装每个关键依赖调用;ratelimit.Server 默认不生效,必须显式传入 limiter 实例。若你在 handler 里直接写 userClient.GetUser(ctx, id) 而不套熔断器,故障就会穿透。
常见错误现象:
- 服务 A 调用服务 B 失败,但没设熔断,重试 3 次后线程池耗尽
- 全局限流中间件配了,但只加在 HTTP 入口,gRPC 接口仍裸奔
- 用了
bbr.NewLimiter,却没配WithBurst,突发流量直接打穿
怎么给 Kratos 服务加熔断?
核心是把下游调用包进 aegis.CircuitBreaker,并绑定到具体方法粒度。不要在顶层 middleware 统一熔断,那会误伤健康接口。
实操建议:
- 在
service/xxx.go中初始化熔断器:cb := aegis.NewCircuitBreaker(aegis.WithErrorThreshold(50), aegis.WithRequestVolume(20)) - 调用前检查状态:
if !cb.Allow() { return errors.New("circuit breaker open") } - 调用后记录结果:
cb.Record(result == nil, time.Since(start)),注意传入 error 判断逻辑 - 避免复用同一个
cb实例去保护多个异构服务(如同时保 DB 和 Redis),应按依赖维度分实例
示例片段(非完整):
func (s *UserService) GetUser(ctx context.Context, id int64) (*v1.User, error) {
if !s.userCB.Allow() {
return nil, ecode.ErrServiceUnavailable
}
defer func() {
s.userCB.Record(err != nil, time.Since(start))
}()
return s.dao.GetUser(ctx, id)
}
限流该放在哪一层?HTTP/gRPC 双协议怎么统一管?
Kratos 的 ratelimit.Server 中间件只作用于 transport 层,HTTP 和 gRPC 需分别配置。若你希望按用户 ID 或 API 路径做全链路限流,不能只靠 transport 中间件 —— 它无法感知业务语义。
推荐组合策略:
- 入口层(HTTP/gRPC Server):用
ratelimit.Server做连接数/请求速率粗粒度防护,防机器打满 - 业务层(Service):用
aegis.TokenBucket或aegis.SlidingWindow按userID、appKey等 key 细粒度限流,需自己从 ctx 提取参数 - 关键 DAO 层:对 DB 查询加
context.WithTimeout+sql.OpenDB().SetMaxOpenConns(),防止连接池耗尽
注意:gRPC 的 metadata 和 HTTP 的 header 提取方式不同,别在 middleware 里硬编码 r.Header.Get("X-User-ID") 去适配 gRPC。
降级和空缓存标记必须手写,没有魔法
缓存穿透和雪崩防护在 Kratos 中完全无默认行为。比如查 Redis 返回空,框架不会自动写 "__NULL__";DB 查不到,也不会自动触发 fallback 返回兜底数据。
必须在 service 层补全逻辑:
- 读缓存后判断:
if val == "__NULL__" { return nil, nil },别直接json.Unmarshal导致 panic - DB 返回 nil 时,主动写空标记:
redis.SetEX(ctx, key, "__NULL__", time.Second*120) - 降级逻辑不能只写在 handler,要下沉到 service 方法内,确保 gRPC 和 HTTP 调用走同一路径
- 避免用
time.Now().Add(24 * time.Hour)设缓存过期,改用time.Second * (3600 + rand.Int63n(300))错峰
最容易被忽略的一点:所有这些防护逻辑都依赖你正确传递 context.Context。如果在 goroutine 里丢了 ctx(比如用 go fn() 而不是 go fn(ctx)),超时、取消、熔断都会失效。











