2.2 Deadline、取消、重试与流式背压
RPC 接口正确不代表调用链可靠。调用方必须限制等待,服务端必须停止无价值工作,重试必须尊重幂等性,流式通信必须限制生产速度。
每次调用都携带 deadline
deadline 表示调用方愿意等待到何时。它应从端到端用户预算向下传播,而不是每一层重新获得完整超时。
入口剩余 900 ms
→ 服务 A 处理 120 ms
→ 调用 B 时剩余约 780 ms
→ B 调用 C 时继续扣除已耗时间传播时应传递剩余时长,避免把不同机器的绝对时钟直接当作共同截止时间;gRPC 的 deadline 传播会扣除已耗时间,但是否自动传播取决于语言与调用方式。还应留出返回响应和清理资源的预算。
默认没有 deadline 的客户端可能长期占用调用相关资源。过短的 deadline 则会制造取消和重试流量。数值应由延迟分布、负载测试和业务预算决定。
客户端 deadline 到期后,服务端框架可以通知取消,但业务代码仍要协作检查取消信号,并把取消传播给派生任务和外部调用。已经提交的数据库事务不会因客户端取消自动回滚。
Status 是契约的一部分
常见 gRPC 状态的语义应稳定:
INVALID_ARGUMENT:参数本身无效;FAILED_PRECONDITION:系统当前状态不允许操作;ABORTED:并发冲突,通常需在更高层重试事务;NOT_FOUND:目标资源不存在;ALREADY_EXISTS:要创建的资源已存在;UNAVAILABLE:暂时不可用,可能适合退避重试;DEADLINE_EXCEEDED:等待截止,写操作结果仍可能未知;UNAUTHENTICATED与PERMISSION_DENIED:没有有效身份凭据和权限不足。
不要把全部业务拒绝变成 INTERNAL,也不要让客户端解析英文 message 决定重试。
Retry 只处理可重试语义
安全自动重试通常要求:
- 操作天然幂等,或服务端持久处理同一幂等键的重复请求;
- 错误明确表示瞬态;
- 总 deadline 尚有预算;
- 尝试次数、退避和随机抖动受限;
- 由一层负责重试,避免多层 SDK/代理叠加尝试次数。
DEADLINE_EXCEEDED 不证明服务端未执行。业务可以另行提供 GetOperation(idempotency_key) 查询接口,或保存首次结果供重复请求复用;上一课的评分查询接口没有实现这个操作查询 API。
下面是仅针对幂等查询 GetScore 的 gRPC service config。它需要由客户端 channel 的配置机制加载,且每次调用仍要设置 deadline。maxAttempts: 3 包含首次调用,所以最多额外尝试两次,所有尝试和退避共用一次逻辑调用的时间预算。
{
"methodConfig": [{
"name": [{"service": "atlas.scoring.v1.ScoringService", "method": "GetScore"}],
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "0.4s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}一旦响应头到达,gRPC 通常把该 RPC 视为已提交,不再按这套策略重试;流中断后的续传仍归应用协议负责。没有显式重试策略也可能发生框架的透明重试,其前提是框架能够判断请求尚未进入服务端应用处理,不能由此推导写请求可随意重放。
长连接仍会断
流式 RPC 需要设计恢复协议:
- 每条消息是否有 sequence/version;
- 客户端从哪个 checkpoint 续传;
- 重连后如何去重;
- 历史保留多长;
- 认证过期如何刷新;
- 心跳与业务空闲如何区分。
只在内存维护会话且没有恢复 token,网络抖动会把所有长流变成从头开始。
背压和流量控制
HTTP/2/gRPC 提供传输层流量控制,但应用仍需限制业务队列。生产者持续生成而消费者变慢时,应:
- 限制在途消息和缓冲字节;
- 等待消费许可或丢弃允许丢弃的数据;
- 对不可丢数据持久化并从 checkpoint 读取;
- 设定单条消息和批次上限;
- 过载时快速拒绝新流。
无界 onNext 循环会把网络背压变成进程内存问题。以每条 64 KiB 的消息为例,只按 1000 条限制队列,原始 payload 就可占约 62.5 MiB,还没算对象开销。计数与字节预算应一起限制。双向流也不能让双方都只写不读;手动流控用错时,两边可能互等窗口,连接很热闹,业务一步没走。
负载均衡与连接生命周期
长连接上的多路 RPC 可能让传统四层负载均衡分布不均。客户端或代理需要感知服务发现、连接老化、实例排空和健康状态。
发布期间先撤出服务发现并启动服务端 graceful shutdown,再等待在途 unary 和 stream 到达明确边界。仅把实例设为 not-ready 不会关闭已经建立的 HTTP/2 连接,也不能保证传播立刻完成。长流应支持迁移或恢复,并设置排空上限;否则 graceful shutdown 可能一直等不到结束。直接终止则可能让大量客户端同时重连。
观测每次尝试与逻辑调用
一次逻辑调用可能包含多次 attempt。指标和 trace 应区分:
- 逻辑请求成功率;
- 每次尝试的状态和延迟;
- retry count 与 retry success;
- deadline 来源与剩余预算;
- 流持续时间、消息速率和取消原因;
- 请求/响应字节和压缩成本。
参考资料
- gRPC, Deadlines
- gRPC, Cancellation
- gRPC, Status Codes
- gRPC, Graceful Shutdown
- gRPC, Retry
- gRPC, Flow Control