超时预算与取消传播
超时用于限制一次等待,deadline 则规定整个调用必须在什么时间前结束。多层服务如果各自重新计算固定超时,最外层请求早已失败,内部任务仍可能继续排队、占用连接并写入结果。
1. 从用户请求建立总预算
入口根据接口 SLO 和调用方容忍度设置总 deadline。例如接口最多允许 800 ms,就要在其中容纳网关、服务处理、下游调用、序列化和返回响应。
预算不是把 800 ms 原样交给每个下游。当前服务先扣除已经消耗的时间和必要收尾时间,再把剩余 deadline 传播出去。并行调用可以共享剩余时限,串行调用则会依次消耗它。
2. 每种超时约束不同阶段
常见参数包括:
- 连接池等待超时。
- DNS、TCP 和 TLS 建立超时。
- 请求写入与响应读取超时。
- 数据库语句和事务超时。
- 线程池或队列等待时限。
- 整个 RPC 或 HTTP 调用 deadline。
只设置 socket read timeout,无法限制连接池排队、DNS 或多次重试的总耗时。应记录每一阶段,并由端到端 deadline 兜底。
3. deadline 沿调用链传播
RPC 元数据可以携带 deadline 或剩余 timeout。接收方取本地剩余时间继续向下传播,避免直接比较不同主机可能偏移的墙上时钟。
服务收到请求时若预算已经耗尽,应在不产生新副作用的前提下尽快拒绝。预算不足以完成一次最低成本操作时,也不应先开始再等必然超时。
内部后台任务若需要脱离原请求继续执行,应显式转成持久化作业,并返回任务 ID;不能悄悄忽略调用方取消。
4. 超时后要主动取消无效工作
调用方停止等待,不代表服务端和数据库自动停止。取消信号应传播到:
- 尚未开始的队列任务。
- 正在执行的 RPC 和 HTTP 请求。
- 可取消的数据库语句。
- 并行子任务和 Future。
- 响应写回和资源清理。
执行代码需要在安全点检查取消,并正确处理中断。已经提交的数据库事务和外部副作用无法仅靠取消撤销,应通过状态查询、幂等和补偿处理。
5. 超时值来自延迟分布与错误成本
超时太短会把正常长尾请求误判为失败,并触发额外重试;太长则让线程、连接和调用方长期等待。可以从目标依赖的延迟分位数、网络差异和允许的误超时率建立初值,再通过压测和生产数据校准。
新连接的 TLS 握手、跨地域网络和冷启动会与连接复用后的请求有不同分布。一个统一的极小超时可能在发布或扩容时集中失败。
6. 服务端还要设置自身资源上限
即使调用方没有 deadline,服务端也不能无限执行。入口应设置最大请求时长,数据库和下游设置更短的内部上限,队列设置最大等待时间。
服务端上限是保护自身的最后边界;调用方 deadline 表达的是当前请求还是否有价值。两者应同时存在。
7. 指标区分主动取消与真实故障
至少记录:
- 请求进入时的剩余预算。
- 各阶段耗时和排队时间。
- deadline exceeded、客户端取消和服务端超时。
- 超时后仍继续运行的任务数量。
- 取消成功率和已产生副作用的情况。
下游日志中的“客户端取消”可能是上游 deadline 正常触发,不应全部计为下游故障;但比例突然升高仍可能说明本层变慢或预算分配不合理。
8. 常见问题
8.1 超时后是否可以直接重试
先判断操作能否安全重复、剩余预算是否足够,以及原请求是否可能已经成功。写操作通常需要幂等键或状态查询;没有剩余时间时,重试只会增加负载。
8.2 Java 线程收到 interrupt 后会自动停止吗
不会。interrupt 是协作信号,代码和阻塞库要响应它。捕获 InterruptedException 后通常应恢复中断状态或结束任务,不能吞掉异常继续无限执行。
9. 面试题
9.1 多级服务调用如何设置超时并传播取消
出现公司:字节跳动
考察重点
- timeout 与端到端 deadline 的区别。
- 阶段预算、剩余时间和取消传播。
- 已提交副作用与可取消任务的边界。
相关内容:第 1 节“从用户请求建立总预算”至第 7 节“指标区分主动取消与真实故障”。
参考回答
入口先根据接口目标设置端到端 deadline,每一层扣除已消耗时间和必要收尾时间,再把剩余预算传给下游,不能让每层重新获得固定超时。连接池、建连、读取、数据库和队列仍可设置各自更短的阶段超时,由总 deadline 限制全部阶段和重试之和。
调用方超时后还要向服务端、Future、数据库和下游传播取消,尽快释放不再有价值的连接与线程。取消无法撤销已提交事务或外部副作用,写操作仍需幂等和状态查询。监控要分别记录阶段耗时、进入时预算、主动取消和真正的依赖失败。