
Rust RPC 超时树一个 deadline 要传到每个下游一、超时不能只写在入口网关分布式 RPC 系统里经常会在入口设置 3 秒超时。但服务内部继续调用下游时如果不传递剩余时间下游可能各自再等 3 秒。最终入口早就超时了后端还在工作资源被浪费重试又叠上来。超时树的核心是 deadline 传播。入口请求确定一个截止时间所有下游调用都基于剩余时间分配预算。越靠后预算越少。这样系统才能在压力下及时停止无意义工作。二、把 deadline 当作请求上下文的一部分每个 RPC 都应该携带 deadline 或 timeout header。服务端读取后再传给下游。flowchart TD A[入口请求 deadline3s] -- B[服务 A] B -- C[下游 B 预算 800ms] B -- D[下游 C 预算 1200ms] C -- E[更下游预算 300ms] D -- F[返回聚合] E -- F预算分配要留缓冲。不要把剩余时间全部给下游否则聚合和序列化没有时间完成。三、Rust 中用 Instant 表达绝对截止时间相对 timeout 容易在多层调用中累积误差。绝对 deadline 更清楚。use std::time::{Duration, Instant}; #[derive(Clone, Copy)] pub struct Deadline { at: Instant, } impl Deadline { pub fn after(d: Duration) - Self { Self { at: Instant::now() d } } pub fn remaining(self) - OptionDuration { self.at.checked_duration_since(Instant::now()) } }下游调用前先看remaining。如果已经没有时间就不要发请求。使用Instant表达 deadline 需注意分布式陷阱Instant::now()在不同节点间不可比较。deadline 跨进程传播时应用墙上时间戳配合误差边界而非直接传Instant。入口在请求 header 放 UTC deadline如X-Deadline: 2026-07-05T12:00:05Z下游收到后用deadline_utc - Utc::now()计算剩余 duration转成本地Instant::now() duration。这里容易出现时钟偏移的两种错误下游时钟慢于上游计算剩余时间偏短下游快于上游可能误以为还有时间但实际已过期。缓解方案是在 header 中附带 creation timestamp下游通过接收时间 - creation_timestamp - 实际耗时校准偏移超过配置阈值时记录告警并采用保守策略。四、超时要和重试一起设计重试会消耗剩余 deadline。一次失败后如果剩余时间不足就不要继续重试。否则重试只会制造压力。还要区分可重试和不可重试错误。超时、连接复位可能可重试业务校验失败不应重试。幂等性也必须确认。没有幂等键的写请求自动重试会制造重复副作用。最后日志里要记录 deadline。排查慢请求时能看到每一层拿到多少预算、花了多少时间、在哪里超时。否则只能看到入口超时无法知道内部时间被谁吃掉。并发调用要预留聚合时间。两个下游同时调用看似只消耗最长那个耗时但结果合并、校验和序列化仍要时间。预算分配时可以给下游 80% 的剩余时间保留 20% 给本层收尾。队列等待也要算进 deadline。请求在本地 worker 队列里排了 500ms下游预算就应减少 500ms。很多系统只在真正发 RPC 时开始计时导致排队时间被隐藏。客户端取消要向下游传播。入口连接断开后如果服务内部继续等待下游资源就会浪费。取消信号和 deadline 一样都是请求上下文的一部分。批量请求还要拆分预算。一个请求里包含多个子任务时不能让第一个慢子任务吃掉全部 deadline。调度层应给每个子任务分配预算并在剩余时间不足时跳过低优先级子任务。五、总结Rust RPC 超时树要把 deadline 作为请求上下文传播到每个下游。下游调用基于剩余时间分配预算重试也必须受 deadline 约束。入口超时只是第一层防线真正稳定的系统要让每一层都知道什么时候该停。