跨服务 Deadline 与取消预算:传播、分配和稳定收敛
每个服务各设 5 秒超时会让整条调用链远超用户预算,客户端断开也不代表下游停止。本文定义可信入口期限、逐跳剩余预算、共享重试额度与可验证取消收敛。
本文要点
- 主题聚焦:TypeScript、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
入口网关 3 秒、服务 A 3 秒、服务 B 再 3 秒,并不会得到 3 秒的用户体验。排队、DNS、连接、重试和响应回传逐层重新计时,最终可能运行十几秒;用户关闭连接后,数据库查询和第三方请求还在继续。MCP 超时、重试与幂等处理单个工具效果,多 Agent 等待图处理持久任务图,本篇只管同步服务调用树的一次端到端预算。
入口签发不可扩大的预算
公开 service.budget.v1。边缘从已认证的 endpoint policy 取得 maxDuration,把客户端声明夹在允许范围内,减去入口已耗时间和响应保留量,生成 operation ID、budget version、可信 ingress 时间、审计用绝对 deadline、初始 remainingBudgetMs、cancel generation 与优先级来源。外部调用者不能靠自定义 header 延长期限或提高优先级。
进程内使用单调时钟计算剩余量;跨机器不要直接比较两个不可靠的墙上时钟。每一跳发送“本地剩余时长减去传输安全垫”,接收方取它、自己的 endpoint 上限和 operation registry 剩余量的最小值,再在本地建立 deadline。gRPC 的 deadline propagation 会把 deadline 转成 timeout 以扣除已耗时间并缓解时钟偏差;其他协议要实现同等规则。任何未知版本、负数、超出上限或来自不可信网络边界的预算都拒绝或按更小本地默认值处理。
W3C Trace Context 只关联观测,不是预算授权。trace 可被采样、重启或跨信任域转发,不能从 span 时间反推可继续执行的许可。分布式追踪记录各阶段耗时,canonical deadline 仍来自调用上下文和受信控制面。
先分阶段,再允许扇出和重试
每个 endpoint 的版本合同冻结最低响应预算、最大排队份额、下游保留、序列化与 flush 余量。调用下游前计算:
childBudget = min(parentRemaining - responseReserve,
dependencyCap,
operationRemaining)
不足依赖的 minimumUsefulBudget 就在发送前快速失败,不能发一个注定超时的请求。并行扇出共享父预算;完成一个 child 不会把用掉的时间“退回”。串行链每跳重新扣除实际耗时。服务网格可以执行连接和 per-try timeout,但Service Mesh 与 Istio配置不能比应用 deadline 更长。
重试与 hedge 消耗同一 retry ledger:记录最大 attempt、已耗时、并发 attempt 和最后可启动时刻。SDK、sidecar 与业务层只能有一个 owner;否则三层各重试三次会放大到 27 次。只有可安全重放的操作才允许 retry,写操作复用稳定 operation/idempotency identity。达到响应保留线后停止新 attempt,即使次数尚未用完。微服务模式中的熔断负责依赖健康,deadline 负责本次调用还能等多久,两者不能互相替代。
取消信号不等于业务已经停止
operation 状态可表示 ACCEPTED -> RUNNING -> CANCELLING -> CANCELLED_STABLE | COMPLETED | NEEDS_RECONCILIATION。每个下游在发送前先向权威 child registry 登记 identity、generation、预算、effect 能力和 PREPARED 状态,再以匹配当前 cancel generation 的 CAS 进入 DISPATCHING;登记失败就不得发送。客户端断开、deadline 到期或上游取消时,控制面原子关闭 child registration watermark、提高 cancel generation,阻止新 child/retry,并按已登记集合传播取消。Go context 等接口仍须显式传入调用链,不能丢进不检查信号的 goroutine。
数据库 driver、RPC 和流处理在阻塞点检查取消并释放连接。每个 child 完成、确认停止或隔离时写终态 receipt;已跨过外部副作用接受点的 child 结果未知时登记 effect ID、payload digest 和 provider handle,进入 NEEDS_RECONCILIATION。只有 registration watermark 已闭合、权威集合中每个 child 都有终态/隔离 receipt、连接与 permit 已释放且旧 generation 无法提交,才能标记 CANCELLED_STABLE。HTTP 499、504 或 gRPC DEADLINE_EXCEEDED不能单独证明静默。
把预算耗尽当成可注入故障
测试用虚拟时钟和可控 transport 注入:入口排队吃掉一半预算、DNS 卡住、三个 child 一快一慢一忽略取消、sidecar 自动重试、发送成功但登记失败、deadline 与 dispatch CAS 竞争、取消消息丢失、旧 retry 启动,以及下游写成功但响应丢失。断言未登记 child 发送数为 0,任何 child deadline 不晚于父级保留线,预算不因跨跳或 retry 增大,旧 cancel generation 不能创建调用,未知效果不会被当成零。
上线门要求 100 万条随机调用树预算扩张为 0、child 预登记与终态 receipt 覆盖率 100%、deadline 后新下游接受数为 0、取消传播到停止新 I/O 的 P99 低于 100 毫秒、孤立连接和 permit 为 0、端到端 deadline miss 低于 0.1%,并分别报告已知取消与待对账效果。先 shadow,再从只读扩到幂等写。异常时按 budget version 回到上一规则,但保留入口硬上限、child registry 和对账;不能回到无 deadline。
延伸阅读
Related