grpc-deadline-exceeded

gRPC 调用返回 DEADLINE_EXCEEDED(code 4),客户端日志为 context deadline exceeded。

现象

客户端在固定超时后收到 DEADLINE_EXCEEDED,流式调用被中断,重试后错误率进一步上升。服务端可能仍在处理,也可能根本没收到请求;监控里客户端错误率和延迟同时升高。

快速定位

先确认客户端 deadline 与服务端实际处理时间,再判断问题在应用、连接还是网络:

GRPC_GO_LOG_SEVERITY_LEVEL=info GRPC_GO_LOG_VERBOSITY_LEVEL=2 ./client
GRPC_VERBOSITY=DEBUG GRPC_TRACE=http,http2,flowctl,transport ./client
grpcurl -plaintext -max-time 5 -d '{"id":"1"}' <host>:50051 package.Service/Method

服务端指标按状态码和分位数看:

sum(rate(grpc_server_handled_total{grpc_code="DeadlineExceeded"}[5m])) by (grpc_method)
histogram_quantile(0.99, sum by (le, grpc_method) (rate(grpc_server_handling_seconds_bucket[5m])))

Go 的 grpc-prometheus 暴露上述指标;OpenTelemetry 对应 rpc.server.duration 和 rpc.grpc.status_code。再查连接与重传:

ss -tnp state established '( sport = :50051 )' | wc -l
ss -tni '( sport = :50051 )'
nstat -az TcpExtTCPTimeouts TcpExtTCPLostRetransmit TcpRetransSegs
grep -i 'too_many_pings\|GOAWAY' server.log

易误判:DEADLINE_EXCEEDED 是客户端在本地 deadline 到期时生成的,不能证明服务端慢。如果服务端 grpc_server_handling_seconds 显示 20ms 完成,问题可能在连接建立、服务端排队、负载均衡或网络。另一个坑:默认 fail-fast 下连接不可用会立刻返回 UNAVAILABLE;开启 WaitForReady(true) 后会一直等到 deadline,表现为 DEADLINE_EXCEEDED,容易误判成超时。

常见根因

  1. 后端处理确实超过 deadline:下游 DB/HTTP 慢、锁等待、CPU 饱和。
  2. deadline 按平均延迟设置,未给下游留余量,或未从入站 context 继续传播。
  3. 客户端连接粘在一台后端:L4 LB 上的长连接不重连,扩缩容后负载不均。
  4. 服务端并发流或工作池耗尽,请求在服务端排队,尚未进入 handler。
  5. 网络丢包/重传、MTU 黑洞,或 NAT 空闲超时切断长连接。
  6. HTTP/2 流控窗口耗尽,请求体大或接收端消费慢。
  7. 服务端优雅发布、MaxConnectionAge 强制断开,客户端未及时重建连接。
  8. 客户端事件循环/线程被阻塞,响应回调延迟,本地 deadline 先到。

修复

相关命令

读完自测

  1. 客户端收到 DEADLINE_EXCEEDED,如何判断是不是服务端处理慢?
  2. 为什么开启 WaitForReady(true) 后更容易看到 DEADLINE_EXCEEDED?
  3. gRPC keepalive 配置不当会触发什么服务端行为?
答案
  1. 看服务端 grpc_server_handling_seconds 和 handler 日志;若服务端很快完成,问题在连接、排队或网络。
  2. 它会让请求在连接不可用时继续等待,直到本地 deadline 到期才返回,于是状态从 UNAVAILABLE 变成 DEADLINE_EXCEEDED。
  3. 客户端 ping 频率超过服务端 EnforcementPolicy.MinTime 时,服务端发送 GOAWAY too_many_pings 并断开连接。