2.2 Deadline、取消、重试与流式背压
RPC 接口正确不代表调用链可靠。调用方必须限制等待,服务端必须停止无价值工作,重试必须尊重幂等性,流式通信必须限制生产速度。
每次调用都携带 deadline
deadline 表示调用方愿意等待到何时。它应从端到端用户预算向下传播,而不是每一层重新获得完整超时。
text
入口剩余 900 ms
→ 服务 A 处理 120 ms
→ 调用 B 时剩余约 780 ms
→ B 调用 C 时继续扣除已耗时间默认没有 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) 查询,或让服务端保存首次结果供重复请求复用。
长连接仍会断
流式 RPC 需要设计恢复协议:
- 每条消息是否有 sequence/version;
- 客户端从哪个 checkpoint 续传;
- 重连后如何去重;
- 历史保留多长;
- 认证过期如何刷新;
- 心跳与业务空闲如何区分。
只在内存维护会话且没有恢复 token,网络抖动会把所有长流变成从头开始。
背压和流量控制
HTTP/2/gRPC 提供传输层流量控制,但应用仍需限制业务队列。生产者持续生成而消费者变慢时,应:
- 限制在途消息和缓冲字节;
- 等待消费许可或丢弃允许丢弃的数据;
- 对不可丢数据持久化并从 checkpoint 读取;
- 设定单条消息和批次上限;
- 过载时快速拒绝新流。
无界 onNext 循环会把网络背压变成进程内存问题。
负载均衡与连接生命周期
长连接上的多路 RPC 可能让传统四层负载均衡分布不均。客户端或代理需要感知服务发现、连接老化、实例排空和健康状态。
发布期间,实例先变为 not-ready、停止接新 RPC,再等待在途 unary 和 stream 到达明确边界。直接终止会让所有客户端同时重连并形成尖峰。
观测每次尝试与逻辑调用
一次逻辑调用可能包含多次 attempt。指标和 trace 应区分:
- 逻辑请求成功率;
- 每次尝试的状态和延迟;
- retry count 与 retry success;
- deadline 来源与剩余预算;
- 流持续时间、消息速率和取消原因;
- 请求/响应字节和压缩成本。
参考资料
- gRPC, Deadlines
- gRPC, Retry
- gRPC, Flow Control