跳到内容

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:等待截止,写操作结果仍可能未知;
  • UNAUTHENTICATEDPERMISSION_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 来源与剩余预算;
  • 流持续时间、消息速率和取消原因;
  • 请求/响应字节和压缩成本。

参考资料

Built with VitePress | Software Systems Atlas