没收到响应,是否可以再发一次

不行,除非请求本身可安全重复,或者应用明确实现并验证了去重。第 13 篇已经看到客户端退出后上游仍可能完成;本篇在同一 inventory 的合成 POST /reserve 上检查响应丢失之后重发。业务输入固定为 operation_id=op-01、X-Request-ID=sm-01-repeat,两次使用相同标识不是应用幂等的证明。

库存第一次已写账本但响应延迟;重发同一操作 ID 再写一次

库存 handler 的顺序是解析操作 ID → 追加内存账本并记录 201 → 人为延迟 400ms → 写响应。客户端第一次等待只有 100ms;看到超时,不知道服务器停在提交前还是提交后。第二次把等待放宽到 2s 并发送同一输入,若没有去重便得到第二笔。对 read 请求的重试也可能增加下游负载,但不会由此自动推出“读请求永远安全”:日志、计费、外部副作用或应用错误实现都要另查。

两层重试如何相乘

假设仅作为配置上界的示意:SDK 对失败调用最多尝试 2 次,每一次调用都被代理各尝试 2 次;一笔用户操作最多触发 2 × 2 = 4 次后端尝试,而不是“额外重试两次”。这不是本仓库实际代理配置或观测比例。并发客户端、多层退避同步、连接池排队和超时预算会扩大排队时间及尾延迟;到达响应或取消时,部分已经发出的尝试无法自动撤回其应用副作用。合理的约束是控制整体 deadline、确定可重试错误条件、限制层数与预算、加入退避抖动,并以相同请求 ID 与 attempt 标识核对每一跳。

幂等键也不是魔法。服务端必须把“该键的操作是否已执行”与结果持久化,并与业务写入作原子决策;相同键携带不同输入要有明确拒绝规则,TTL 到期后的重发要有业务约束。当前示例只把 operation_id 存入内存列表,刻意不去重,重启后列表也消失;不能拿这份账本当作可上生产的幂等实现。Envoy、Istio 和客户端 SDK 的重试可以改善某些瞬时故障的可用性,不能为非幂等操作提供事务或“恰好一次”。

正常与失败路径的进程实验

在 examples/service-mesh/ 执行:

1
GO_BIN=/tmp/service-mesh-tools/go/bin/go bash scripts/run-01.sh

脚本直接访问累计工程的合成库存服务,先让写操作成功提交而客户端超时,再人工重发同一 operation_id;还用缺少必要字段的输入验证 400 不能写入账本。examples/service-mesh/evidence/01/20261001T140937Z-2620941/ 保存代码校验值、Go/Node 版本、完整命令、每次原始输出/退出码及账本:第一次 curl 退出 28;首次查询账本已有序号 1;再次发送退出 0;最终账本有序号 1、2 且同为 op-01。非法输入 curl 返回 22,账本未增加第三笔,四项断言与 go test ./... 通过。失败断言改造临时证据后返回非零的机制在 checks/verify-01.mjs 可见。

这是一组人工重发,不是代理自动重试;读者重跑可比较账本和请求 ID,但不能据此声称 Istio 对 POST 的任何默认重试政策已经验证。待隔离集群可用后,需分别关闭 SDK/代理重试、单独开启各层、再组合:保存代理重试策略、active route、每次上游 attempt 的 ID/原因、库存账本与客户端结果;配错策略的负例必须让断言非零。Envoy 未可用、Istio/Kubernetes 尚未冻结,本项自动重试验证 NOT_RUN。

练习与参考解答

  1. 超时后的重发携带相同 operation_id,能保证只入账一次吗?参考解答:不能。例子先提交再延迟响应,重发后同一 ID 对应两条账本记录;需要服务端原子地关联幂等键、请求参数、状态与结果,不能只靠代理记住历史请求。
  2. 将客户端重试限制为最多 2 次、代理重试限制为每次最多 3 次,理论最多有几次后端尝试?怎样构造反例说明总请求数不一定等于此上界?参考解答:在每层所说的次数都是总尝试次数时上界为 2 × 3 = 6;第一次就成功只需一次,整体 deadline 提前耗尽也可能少于六次。若配置写的是“额外重试次数”,还须把首试加进去,不能混用定义;以代理尝试日志验证真实数量。

官方参考与系列导航

系列导航:00 无网格基线 → 13 超时与取消 → 14 重试与副作用(本文)→ 15 连接限制与异常摘除。