第 7 章:重试之前,先判断自己知道什么
“没有看到回答”不是“没有发生执行”的证据。请求可能已经被上游接收,只是响应在网络中丢失;模型服务可能已消耗资源,工具也可能已经产生外部动作。重试策略首先是语义与授权问题,其次才是次数和退避算法。
7.1 HTTP 方法对自动重试有约束
RFC 9110 第 9.2.2 节定义幂等方法,并对自动重试提出限制,其中明确禁止代理自动重试非幂等请求。不能因为接口路径中有 generate,就把任意 POST 当作可安全重放。
应用可以定义有明确去重、接受确认和操作合同的编排协议,但那需要端到端保证。单纯收到 503、连接断开或者尚未看到 token,都不能构成该保证。请求体中的一个 request_id 也只有在上游实际据此去重时才有用。
7.2 本地演示为何允许一个受限分支
实验默认 allow_failover=false。调用者显式允许切换,路由配置又把该替身列入可信清单,且替身返回 HTTP 503 和私有信号 X-Lab-Execution: not-started 时,编排器才允许在受限次数内选择下一个合格上游。
这里的信号由本地受控替身产生,表达“该实验操作尚未开始”。这是教学应用协议的一部分,不是通用 HTTP 503 的含义。未列入信任范围的服务即使返回同名字段,也不应获得同样待遇。
| 情况 | 实验的动作 | 原因 |
|---|---|---|
| 未显式允许切换 | 不发起第二次尝试 | 调用者没有授权 |
| 可信替身明确未开始,且满足授权 | 最多进入一次后续尝试 | 有受控合同支撑 |
| 普通 503 | 不透明切换 | 无法确认是否已执行 |
| 发送后超时,尚无事件 | 保留未知,不重放 | 没有输出不等于没执行 |
| 已发出事件后断流 | 标记不完整,不拼接另一段生成 | 下游已观察到结果 |
7.3 幂等不等于零成本
一个真正的幂等接口保证重复操作的预期效果与执行一次相同,并不自动保证每次尝试都不占用资源。去重记录可能有过期时间,身份与参数也可能影响去重范围。设计重试时要写明这些条件,并保留每次尝试的成本证据。
如果上游支持幂等键,网关需要验证键的作用域、参数冲突行为、保留期限,以及流式结果是否可以重取。不同上游之间通常没有共享去重账本,不能直接把同一个键发给 B,就认为 A 与 B 的两次执行会被统一合并。
7.4 次数、期限与拥塞共同限制尝试
重试需要同时受总次数、总期限和资源预算约束。退避与抖动用于分散请求恢复时刻,但不能把本来不安全的操作变成安全操作。熔断器也只是根据故障信号限制流量,不会撤销已执行的任务。
实验为便于观察,只设置很小的尝试上限,没有实现完整的生产退避调度器。生产方案应避免每层各自重试造成次数相乘,并把上游重试、SDK 重试和业务重试一起纳入尝试审计。
练习。 网关最多两次,SDK 最多三次,业务任务又重跑两次。在没有共享尝试预算时,可能产生多少次上游调用?这个数字为什么仍然不能单独描述业务副作用风险?