服务网格 01:SDK、网关与服务网格分别处理什么
同一请求,四处可能设置超时
上一期 第 00 篇 让 loadgen → frontend → orders → inventory-v1 / external-stub 的只读调用先跑通。下一步不是立即把所有网络选项移到代理,而是回答:调用方 SDK、入口网关、工作负载代理、应用分别知道什么?假设 frontend 对 orders 超时重发、orders 也对 inventory 重发,最后客户端只看到一次失败,这些重发造成的写入次数不一定是一次。
先修是区分连接失败、HTTP 响应与业务结果;这里讨论的是职责,不预设已有 Istio 集群。本篇记录的副作用实验使用本机进程直接访问 inventory,没有运行网关或网格代理。
图中业务请求由客户端经入口网关抵达 frontend;frontend 的 SDK 发起下游调用,若有 sidecar 则相邻的代理可能处理这段流量;orders/inventory 应用仍需决定一次业务操作能否重复执行。Istiod 的控制路径不在图中的业务转发链里。
职责不按功能名称,而按可见信息划分
| 位置 | 通常能看到什么 | 适合做什么 | 不能据此保证什么 |
|---|---|---|---|
| 客户端 SDK(进程内) | 方法、调用上下文、剩余 deadline、业务操作 ID | 传播截止时间与请求标识,按调用语义决定是否重试 | 仅凭客户端超时推断服务端未执行 |
| 入口网关(南北向) | 进入系统的 host、路径、TLS 连接和可获得的身份信息 | 接入路由、入口 TLS 终止与边界策略 | 自动覆盖内部服务间的全部调用,或完成业务去重 |
| 工作负载代理(东西向,sidecar 示例) | 可解析的 HTTP/gRPC 元数据、连接和可配置的对等身份 | 在支持的流量路径上应用路由、超时、重试等代理策略 | 读懂订单扣减的业务语义,或回滚已写入的库存 |
| 应用与存储 | 操作 ID、事务状态、副作用结果 | 原子去重、返回已有操作结果、保持数据一致性 | 单独代替网络入口或代理提供拓扑与传输治理 |
这些只是常见边界,不是“配置了策略就已拦截请求”的承诺。客户端可能绕过某个代理,入口网关不一定介入服务间调用,TLS 也不意味着最终用户已认证。具体配置与流量是否经过代理,需要先看资源,再查数据面当前状态,最后用带标识请求及后端记录证明。Istiod 提供控制面配置,通常不转发这些业务请求。
同一个预算可以跨层传递,但不能把每层超时独立设为“最大可用时间”。例如客户端只肯等待 100 ms,服务端允许处理 400 ms,则前者超时不取消已经提交的写入。不同位置叠加重试还可能放大尝试次数;不能据此推出确切倍数,必须先知道哪些位置实际启用了重试和每跳的尝试上限。第 13–14 篇将单独实测代理策略。
用同一个副作用账本检验边界
在仓库 examples/service-mesh/ 执行,需 Go 1.25.1、Node 22 与 curl:
1 | |
脚本启动本地 inventory-v1,向 POST /reserve 发送合成 operation_id=op-01;服务端先向内存账本追加记录,再人工延迟响应 400 ms。curl 限时 100 ms,因此客户端报超时;再查账本,已经有序号 1。客户端重新发送同一个操作 ID(这次等待 2 秒),又得到序号 2;不带操作 ID 的请求得到 HTTP 400,账本不变。服务只监听 loopback,退出后账本消失;它不代表持久业务数据库。
原始证据在 examples/service-mesh/evidence/01/20261001T122011Z-2497728/:environment.txt 记录工具链和源码哈希,*.command/*.exit/*.stdout/*.stderr 保存请求、退出码、响应与服务日志,assertions.txt 给出四项判据。100 ms 请求的 curl 退出码是 28,第一次查账本显示一条,第二次查账本显示两条;非法输入的 curl 退出码是 22。这是客户端重发的实测,不是“已经运行 Istio 代理重试”的结果。 即便下一篇增加代理,业务去重的责任也不会转移到代理。
如何改造“超时后重试”的设计
如果业务要求 op-01 只扣减一次,应用需在持久存储中按操作 ID 原子记录“结果与是否已处理”:第二次同 ID 请求返回既有结果,不再执行写入。只把操作 ID 放在 HTTP 头中而不实际去重,没有效果。本例恰好没有做去重,所以两次写入能构成反例;进程重启将清空账本,不能用作真正的幂等实现。若客户端对未知结果直接放弃,仍需能够查询操作状态或人工对账;代理的连接成功、超时或重试次数都不能独自解释最终业务状态。
练习与参考解答
- frontend 的 SDK 超时并立即重试,orders 的代理也各重试一次;理论上 inventory 最多看到几次尝试?参考解答:若 SDK 最多两次、每次到 orders 的代理最多两次、orders 对 inventory 的调用每次再尝试两次,在所有层都启用且请求均到达的条件下是
2 × 2 × 2 = 8次;这只是上界计算,不是本次实验观测。每层实际路径、配置与重试条件必须另外验证。 - 把服务端
-reserve-response-delay改为0,再以同一个操作 ID 发两次;能否证明“没有超时就能自动去重”?参考解答:不能。若两次请求都到达,本例仍在同一内存账本追加两条;改变延迟只改变客户端是否看见响应,不改变服务端是否实施原子去重。可用GET /ledger核查操作 ID 和序号;若迁移到真实服务,要另查持久化事务与并发竞争。
官方资料与系列导航
- Istio 架构 与 流量治理概念:控制面/数据面分工与代理可治理的流量。
- Gateway API 总览:网关资源模型与作用域;本篇未在集群验证实现支持。
- RFC 9110 §9.2.2:HTTP 方法的幂等语义,不等于业务操作的去重实现。
系列导航:00 一次服务调用需要哪些基础条件 → 01 SDK、网关与服务网格分别处理什么(本文)→ 02 Service 地址怎样到达 Pod。
