响应失败了,但业务已经提交
目标
比较真实 Istio 重试与业务记录,实现事务边界:即使出现响应超时或进程终止,也能防止同一业务重复执行。
为什么重要
200 并不能证明业务只执行了一次,504 也不能证明已经回滚。如果同一订单的每次传输尝试都生成新的业务键,防重机制也会失效。反过来,把内容相同的不同订单合并,同样是错误。本实验会同时处理真实服务网格中的合成 HTTP 业务,以及独立事务代码修改。学生代码修改会应用于临时 SQLite 测试,不会自动替换 HTTP 服务器代码。
步骤
- 把 python3 /opt/fixtures/ica-retry/runtime.py inventory 的全部输出保存到 /root/ica-retry/inventory.json。namespace 为 ica-retry,并且 client 与 upstream 的真实代理必须就绪。
- 在 /root/ica-retry/routes.yaml 中创建 networking.istio.io/v1 VirtualService retry-lab。namespace 为 ica-retry;hosts 与所有目标均为 upstream.ica-retry.svc.cluster.local;端口为 8080。第一条路由 flaky 对 /flaky 和 /naive 两个 exact match 生效,timeout 为 1s,attempts 为 2,perTryTimeout 为 1s,retryOn 为 '503',retryIgnorePreviousHosts 为 false。第二条路由 lost 对 /lost exact 生效,timeout 为 500ms,attempts 为 0。最后一条 control 不设置 match,指向相同目标,attempts 为 0。应用后执行 collect retry,比较 retry.json 中真实的接收次数与业务次数。
- 对同一工具执行 collect timeout。timeout.json 必须同时包含 /lost 的客户端 504、服务器已提交的响应,以及使用同一键调用 /charge 后的恢复结果。不要直接编造数字写入文件。
- 通过 collect conflict 保存 conflict.json。确认使用同一业务键、把金额从 100 改为 200 的请求会收到 409,而现有业务金额仍保持为 100。
- 通过 collect durability 保存 durability.json。工具只会真正删除并重新创建这台 VM 内的 upstream Pod。client UID 应保持相同,upstream UID 与服务器 instance 必须发生变化;同一业务键和响应 ID 则必须保留。
- 把 /opt/fixtures/ica-retry/broken.py 复制为 /root/ica-retry/transaction.py,修复 charge 默认执行路径中的分离提交缺陷。保留 initialize、charge、Conflict,以及 after-business、after-key、after-commit 强制终止点。提交前,业务与键的数量都应为 0;提交后,两者都应为 1;重试后,业务数量始终必须为 1。
- 使用 transaction_probe.py /root/ica-retry/transaction.py identity 验证 8 个使用相同键的并发请求。新处理只能有一个;同一键的客户或金额变化必须产生 Conflict;JSON 顺序变化应得到相同响应;不同键但内容相同的请求应作为独立业务。无效金额和未知字段应通过 ValueError 拒绝。
- 在 /root/ica-retry/incident.json 中记录:额外尝试 2、最大尝试 3、naive 业务 3、防重业务 1。timeout_means_rollback 与 external_payment_exactly_once 为 false。changed_payload_status 填写真实冲突代码,durable_scope 填写 same-vm-disk,atomic_scope 填写 single-sqlite-file。保留此前所有真实观测,用于最终评分。
参考
- 命令示例:python3 /opt/fixtures/ica-retry/runtime.py collect timeout。如果观测文件已存在,会保留既有结果。需要重新测试时,请先把自己的观测文件改名保存,再执行命令。
- 初次 apply 的策略需要时间传播到 Envoy。如果 collect 报告路由不匹配,请检查真实 proxy-config routes。
- broken.py 是用于复现分离提交事故的故意错误答案。评分不会修改学生文件,而会在临时数据库中执行。
- 不要把负载实验的成功比例或响应速度本身背成答案,应查看每个业务键的状态。
- 本实验只讨论同一 VM 的磁盘持久性,不保证 VM 终止、断电或外部支付的 exactly-once。不要放入个人资料或真实支付信息。
记录真实代理与 Pod 身份
使用 inventory 命令记录两个 Pod 的 UID。不要只看 Running 名称,还要确认真实代理已经就绪。
区分三次接收与三次业务执行
应用 routes.yaml 并执行 collect retry。attempts 表示额外尝试次数,应分别查看 /naive 与 /flaky 的业务账本。
在 504 后恢复已经提交的业务
确认 /lost 的预算为 500ms 且不重试。collect timeout 会使用同一业务键再次查询 /charge。
拒绝同一键对应的不同金额
collect conflict 的两个请求使用相同业务键,只有金额不同。确认现有业务与响应是否得到保留。
更换 Pod 后仍返回同一业务响应
collect durability 只会替换这台 VM 中的 upstream。Pod UID 与服务器 instance 应改变,业务 ID 则必须相同。
通过代码修复分离提交缺陷
把 broken.py 复制为 transaction.py 并阅读代码。查找 charge 默认执行路径中,业务写入与防重记录之间是否夹着一次提交。
并发请求与独立业务的边界
使用相同键的并发请求应合并为一个业务,不同键但内容相同的请求则应保留为独立业务。同时确认内容变化会被拒绝,并检查 JSON 顺序变化。
区分响应与业务的事故报告
区分最大尝试次数与真实业务效果,并把保证范围限制为单个 SQLite 文件与同一 VM 磁盘。