gRPC 生产实践:Deadline、Retry、健康检查与连接复用

gRPC 提供高效传输和强类型接口,但生产可靠性仍取决于超时、重试、连接管理和可观测性。默认配置能跑通 Demo,不代表能在下游变慢、连接抖动和滚动发布时稳定运行。

每次调用都要有 Deadline

没有 Deadline 的请求可能一直占用 Goroutine、连接和下游资源。入口服务应根据总 SLA 分配预算,并把剩余时间传给下游:

ctx, cancel := context.WithTimeout(parent, 800*time.Millisecond)
defer cancel()

resp, err := client.GetUser(ctx, req)

不要每一层都重新给固定 800ms,否则三层串行调用可能超过入口预算。服务端收到取消后应停止数据库查询和后续任务,避免客户端已离开、服务端仍继续工作。

重试必须满足三个条件

  1. 错误确实是暂时性的,例如连接短暂不可用。
  2. 方法具备幂等性,重复执行不会产生额外副作用。
  3. 重试仍在原始 Deadline 与次数预算内。

对创建订单、扣款等写操作,应使用业务幂等键,而不是因为 gRPC 支持重试就直接打开。退避应带随机抖动,避免大量客户端同时重试形成流量尖峰。服务配置中的 Retry Policy 需要限制最大尝试次数和可重试状态码。

连接应复用

不要为每个请求新建 ClientConn。连接建立涉及 DNS、TCP、TLS 和 HTTP/2 协商,频繁创建会增加延迟和端口消耗。通常每个目标复用长期连接,由 gRPC 管理子连接和负载均衡。

高并发长流场景可能受单个 HTTP/2 连接并发 Stream 限制,但增加连接池之前应先测量。过多连接会加重服务端和负载均衡器负担。连接池只在 Profile 证明单连接成为瓶颈时引入。

Keepalive 不是越频繁越好

Keepalive 用于发现失效连接和维持必要长连接,客户端与服务端参数必须协调。过于频繁的 Ping 会制造额外流量,并可能被服务端以 too_many_pings 关闭。普通短请求通常不需要激进配置。

标准健康检查与优雅关闭

实现 gRPC Health Checking Protocol,让编排系统和客户端负载均衡能识别 Ready 状态。服务退出时先停止接收新流量,再等待进行中的 RPC 完成:

done := make(chan struct{})
go func() {
    server.GracefulStop()
    close(done)
}()

select {
case <-done:
case <-time.After(20 * time.Second):
    server.Stop()
}

滚动发布时,Readiness 先下线、连接排空、再退出进程,能显著减少 Unavailable

可观测性最少包含什么

  • 方法名、状态码、耗时分布、请求与响应大小。
  • Deadline Exceeded、Canceled、Unavailable 分开统计。
  • Trace Context 通过 Metadata 传播。
  • 服务端日志记录请求 ID,不打印敏感 Payload。
  • 客户端重试次数和最终结果同时记录。

上线清单

每个 RPC 有 Deadline;写方法有幂等策略;连接被复用;重试有预算与抖动;实现标准健康检查;发布支持优雅排空;指标能区分调用方、方法和状态码。

参考资料