← 返回题目列表

为什么分布式调用要设计请求预算而不是单个超时?

中等 第 22 / 27 题 更新于 2026/09/30
Deadline超时预算延迟取消传播
学习 AI 实战项目

简化版

单个超时只限制某一跳,无法保证整条调用链在用户期限内完成。请求预算以端到端 deadline 为上限,每一跳根据剩余时间分配子调用、重试和清理时间,并在预算耗尽时取消无价值工作,从而避免层层等待把延迟和资源占用不断放大。

详细版

如果入口要求 1 秒返回,而 A 调 B、B 调 C 都各配置 1 秒,最坏耗时会超过 2 秒,重试后更长。正确方式是在入口确定 deadline,沿链路传播;每层读取剩余预算,扣除本地处理、网络返回和安全余量,只给下游更短的时间。

预算还应覆盖排队和重试。请求在线程池排队 600ms 后只剩 200ms,就不应再发预计耗时 500ms 的下游调用;一次尝试失败后,只有剩余时间足够且操作可幂等时才重试。扇出调用则给各分支共同 deadline,并根据必选/可选结果决定提前返回或降级。

实现中使用单调时钟计算本进程耗时;跨进程可传播绝对 deadline,但要考虑时钟偏差,常同时设置最大本地超时。deadline 到期后传播取消并释放线程、连接和查询,不过取消是尽力而为,不能被当作服务端业务回滚。

完整版教学

一、单跳超时为什么会突破用户目标

局部超时只回答“我最多等下游多久”,没有回答“用户总共还能等多久”。调用链越长,局部上限越容易累加;如果每层还各自重试,真实尾延迟会远超入口预期。

假设用户目标 800ms,网关调用 A 超时 800ms,A 调 B 也超时 800ms。A 在 750ms 时才调用 B,即使 B 在 100ms 内返回,用户也已等待 850ms。B 的工作对这次请求已经失去价值。

记忆钩子:timeout 属于某一跳,deadline 属于整次请求;每往下一跳,预算只能减少不能重置。

二、如何逐层分配预算

入口根据产品延迟目标创建 deadline,各层先扣除已经消耗的时间,再为序列化、返回和本地收尾预留安全余量。下游超时取“自身默认上限”和“剩余预算减余量”中的较小值。

端到端预算:1000ms
网关与网络:已耗 60ms
A 本地计算:预计 90ms
返回与安全余量:100ms
B 最大可用预算:1000 - 60 - 90 - 100 = 750ms

预算不是静态平均分配。数据库查询可能需要 300ms,缓存只需 20ms,应根据历史分位数和业务重要性分配,并持续校准。

三、排队时间必须计入预算

很多系统只在真正发起 RPC 时启动超时,忽略请求在线程池、连接池和队列中的等待。过载时请求可能排队 900ms 后才拿到 500ms 调用超时,造成系统继续处理用户早已放弃的工作。

请求进入系统时就应携带 deadline。出队前检查剩余时间,过短则快速失败或降级;数据库连接获取、限流等待、锁等待也要受同一预算限制。这样过载时能主动丢弃已无价值的工作,帮助系统恢复。

Little 定律提醒我们:吞吐固定时,停留时间变长会增加在途请求数。预算控制既是用户体验机制,也是资源保护机制。

四、重试怎样消费预算

重试不是额外赠送的时间。第一次尝试 300ms 后只剩 400ms,退避 100ms 后第二次最多只有 300ms;若预计下游 P95 是 350ms,就不应再试。重试次数上限之外,还应有时间预算和全局流量预算。

剩余预算错误类型建议动作
700ms连接建立失败退避后换节点重试一次
150ms下游 P95=300ms不重试,降级或返回
500ms参数错误不重试,立即失败
600ms写请求超时同幂等键查询/重试
0ms任意结果取消下游并结束请求

hedged request 也会消耗额外容量,只适合幂等读和长尾明确的场景,且要延迟发起与限制比例。

五、扇出调用如何安排共同期限

聚合页可能并行请求库存、价格、推荐和评价。若等待所有分支,各自 99% 正常也会因最慢分支抬高整体尾延迟。应区分必选和可选依赖:价格和库存必须成功,推荐超时可省略。

共同 deadline 500ms
必选:价格、库存 -> 任一失败则明确失败/安全降级
可选:推荐、评价 -> 350ms 截止,留 150ms 聚合返回

不能给四个分支各 500ms 后再额外聚合。提前为聚合和输出预留时间,也要限制 fan-out 并发,避免一次请求耗尽连接池。

六、跨进程传播与时钟问题

绝对 deadline 便于各层计算剩余时间,但机器时钟有偏差。RPC 框架通常将 deadline 转换为相对 timeout 传输,接收端再基于本地单调时钟倒计时;应用还应设置合理最大值,防止错误的远期 deadline 造成无限等待。

单进程测量耗时应使用 monotonic clock,避免 NTP 回拨让剩余时间增加。日志可同时记录墙上时间、原始 deadline 和本地计算出的剩余毫秒,便于排查预算在哪一层耗尽。

对于异步消息,在线请求 deadline 通常不应直接成为消息永久失效时间;应另行定义业务截止时间,例如“订单 15 分钟未支付则关闭”。

七、取消传播和观测

deadline 到期后,调用方要取消仍在执行的子任务,数据库驱动也应支持查询取消或 statement timeout。否则入口虽然返回,底层仍持续消耗资源,形成 orphan work。取消是尽力而为,已提交事务不会自动撤销。

指标应分解 DNS、连接、排队、本地处理和下游耗时,记录请求入站剩余预算、出站预算、deadline exceeded 和取消后继续运行时长。只看总 P99 无法判断预算被谁消耗。

incoming_budget_p50_ms: 720
queue_wait_p99_ms: 85
outgoing_budget_p50_ms: 430
deadline_exceeded_rate: 0.4%
orphan_work_p99_ms: 12

八、常见误区与追问

  • 误区:每个服务统一配置 1 秒超时就够了。 串行调用会累加,无法满足端到端目标。
  • 误区:超时从发 RPC 时开始计算。 线程池和连接池排队也消耗用户等待时间,必须计入预算。
  • 误区:deadline 到期代表服务端已停止。 取消可能迟到或不被支持,副作用需要幂等和结果查询。
  • 误区:剩余时间都可以交给下游。 还要预留本地聚合、序列化、返回和清理时间。
  • 追问:deadline 与 timeout 有什么关系? deadline 是绝对结束点,timeout 是某一步允许持续的相对时长,后者应由前者剩余量约束。
  • 追问:如何给并行分支分预算? 设置共同总期限,按必选和可选依赖给更早子截止点,而非简单把时间相加。
  • 追问:预算太短时快速失败会不会降低成功率? 会减少已无机会按时完成的请求,却释放资源,过载时反而能保护整体成功率。

九、加强记忆

按“入口定截止、逐跳减预算、排队也计时、重试要付费、到期即取消”回答。端到端 deadline 是整条链路共同遵守的时间契约,每个服务只能消费剩余部分并给返回留余量。这样既控制用户尾延迟,也能清理已经失去价值的工作,防止超时请求继续拖垮系统。