Kratos 默认 P2C 比轮询更抗抖动,因其每次随机选两个节点并基于 EWMA 延迟择优,避免持续命中异常节点,实测 P99 延迟波动降低约 37%。

为什么 Kratos 默认的 P2C 负载均衡比轮询更抗抖动
Kratos 客户端默认用 P2C(Pick of Two Choices)而非 RoundRobin,不是为了“炫技”,而是应对真实高并发下节点响应毛刺。轮询会把请求均匀打到所有节点,一旦某个实例因 GC 或慢查询延迟飙升,后续请求仍会持续命中它,形成雪崩前兆。
P2C 每次从服务发现列表里随机挑两个节点,比较它们的 EWMA 延迟值(指数移动平均),只选更健康的那个。这个机制天然规避了“坏节点被持续轮到”的问题,实测在 5000 QPS 下,P99 延迟波动降低约 37%。
- 延迟指标每 3 秒强制刷新一次,防止节点长期“饥饿”导致统计失真
- 不依赖外部健康检查探针,完全基于真实请求反馈,开销低
- 若你手动替换成
RoundRobin,需同步补上超时熔断逻辑,否则故障扩散更快
gRPC 连接复用和 Keepalive 怎么调才不踩坑
Kratos 的 grpc.Dial 默认不开启 Keepalive,高并发短连接场景下容易触发 TIME_WAIT 爆满、连接创建耗时陡增。必须显式配置,但参数组合极易出错:
-
KeepaliveParams中Time(发送心跳间隔)必须 ≤Timeout(等待响应超时),否则心跳发不出去就被断连 - 生产环境建议设为
Time: 30s,Timeout: 10s,避免被云厂商 LB(如阿里云 SLB)的 60s 空闲断连策略误杀 - 务必启用
WithBlock()配合WithTimeout(),否则grpc.Dial可能返回未就绪连接,首次 RPC 直接报context.DeadlineExceeded
错误示例:grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Second}) —— 1 秒心跳在多数内网环境过于激进,引发无效探测风暴。
HTTP Server 启动时怎么避免 goroutine 泄漏
Kratos 的 http.NewServer 底层用 net/http.Server,但直接传入自定义 Handler 且未处理 Context 生命周期时,高并发下易堆积阻塞 goroutine。常见泄漏点:
- 业务 handler 里启动 goroutine 但没监听
ctx.Done(),请求取消后协程继续跑 - 中间件中用了
time.AfterFunc或select等待超时,但没绑定请求上下文 - 忘记调用
httpSrv.RegisterMux就直接app.Run(),导致某些路由走默认 mux,日志中间件失效而无法定位长请求
正确做法:所有异步操作必须基于 ctx,例如 go func(ctx context.Context) { ... }(r.Context()),并在函数开头加 select { case 。
Protobuf 编解码性能瓶颈在哪,怎么绕过
高频小包场景(如订单状态轮询),proto.Marshal/proto.Unmarshal 可能占 CPU 15%+。Kratos 的双协议映射本身不增加额外开销,但默认行为有优化空间:
- 禁用反射解码:确保
protoc-gen-go生成代码时用--go_opt=paths=source_relative,否则运行时 fallback 到反射路径 - 小对象复用:对固定结构的 request/response,用
sync.Pool缓存*HelloRequest实例,避免频繁 malloc - 别在 middleware 里做完整 proto 解析——比如鉴权中间件只需读
Authorizationheader,没必要 Unmarshal 整个 body
真正卡住的往往不是编解码本身,而是 protobuf 生成代码里隐含的 sync.Once 初始化竞争,尤其在服务刚启动的 burst 请求期。这点容易被 profiling 工具忽略。



















