对付一个会丢响应的支付 API
目标
亲手制造超时后的重试如何造成重复支付, 再使用幂等键阻止它。还要确认即使添加了键也不起作用的情况。
环境
/opt/app/pay.py 模拟客户的支付 API。它不是要修改的对象,
而是对端系统。
mkdir -p /root/idem
nohup python3 /opt/app/pay.py > /tmp/pay.log 2>&1 &
sleep 1
curl -s http://127.0.0.1:8021/health
POST /payments 결제 하나를 기록한다
Idempotency-Key 헤더가 있고 이미 본 키면
새로 기록하지 않고 저장해 둔 결과를 그대로 돌려준다
GET /payments 지금까지 기록된 결제 전부
POST /reset 상태 초기화
偶数次请求完成处理后,只将响应延迟 6 秒。 若设置 2 秒超时, 客户端会认为失败,但服务器已经完成记录。这就是 “不确定结果”。
要创建的文件
全部位于 /root/idem/ 下。
naive.sh 멱등키 없이. 타임아웃이면 다시 보낸다
naive.txt 그 결과와 왜 그런지
safe.sh 비즈니스 행위 하나에 키 하나. 재시도에도 같은 키
newkey.sh 시도마다 새 키를 만든다 (일부러 틀린 형태)
newkey.txt 그 결과와 키를 무엇 단위로 만들어야 하는지
backoff.sh 대기 시간 간격을 낸다
retryable.sh 상태를 받아 재시도 여부를 답한다
payments.csv 조사할 결제 기록
forensics.txt 중복을 찾고 원인을 지목한 결과
report.md 정리
评分方式
评分程序会每次重置服务器并直接运行你的脚本后统计记录。 它检查的是实际留下的记录,而不是你写下的数字。
naive.sh 기록 > 주문 이어야 한다 (중복이 생겨야 한다)
safe.sh 기록 = 주문 이어야 한다 (정확히 한 번)
newkey.sh 기록 > 주문 이어야 한다 (키가 무력해진다)
backoff.sh 간격이 늘고, 두 번 돌리면 달라야 한다
步骤
- 启动支付 API。
naive.sh——不使用幂等键,支付至少 4 笔订单;发生超时就重新发送。 将结果写入naive.txt。safe.sh——为每个订单确定一个键,重试时再次使用同一个键。newkey.sh——添加键,但每次尝试都重新生成。在newkey.txt中 说明为什么这样不起作用。backoff.sh——输出至少 4 个重试间隔。间隔应按指数增长, 并包含随机性。retryable.sh——接收状态(500、429、400、timeout等)作为参数, 输出yes或no。- 创建
payments.csv并查找重复项。重复记录之间的时间间隔 会说明原因。 - 进行总结。
参考
第 7 步的材料按如下方式创建。
cat > /root/idem/payments.csv <<'CSV'
payment_id,order_id,created_at
PAY-1,ORD-1,2026-09-07T10:00:00Z
PAY-2,ORD-2,2026-09-07T10:00:05Z
PAY-3,ORD-2,2026-09-07T10:00:06Z
PAY-4,ORD-2,2026-09-07T10:00:08Z
PAY-5,ORD-2,2026-09-07T10:00:12Z
PAY-6,ORD-3,2026-09-07T10:01:00Z
PAY-7,ORD-4,2026-09-07T10:02:00Z
PAY-8,ORD-4,2026-09-07T10:02:01Z
PAY-9,ORD-4,2026-09-07T10:02:03Z
PAY-10,ORD-4,2026-09-07T10:02:07Z
CSV
如果间隔呈 1 秒、2 秒、4 秒的倍增形式,原因几乎可以确定。
启动会丢失响应的 API
启动支付 API。
在后台启动 /opt/app/pay.py,确认 /health 返回 200。开始前通过 /reset 清空状态。
将超时视为失败时
naive.sh——不使用幂等键,支付至少 4 笔订单;发生超时就重新发送。
将结果写入 naive.txt。
使用 curl --max-time 2 设置超时,失败时通过 || 再发送一次。至少 4 笔订单。然后通过 GET /payments 统计实际记录。
使用幂等键实现恰好一次
safe.sh——为每个订单确定一个键,重试时再次使用同一个键。
为每个订单确定一个键,重试时也使用同一个键。两次调用都添加 -H "Idempotency-Key: $K"。
添加键仍不起作用的情况
newkey.sh——添加键,但每次尝试都重新生成。在 newkey.txt 中
说明为什么这样不起作用。
尝试在每次调用时生成新键。服务器会将其视为不同请求并再次记录。键应针对“一次业务行为”生成,而不是“一次 HTTP 请求”。
增大并打散间隔
backoff.sh——输出至少 4 个重试间隔。间隔应按指数增长,
并包含随机性。
间隔每次都必须增大(指数退避),而且运行两次时数值应不同(抖动)。没有抖动时,所有客户端都会在同一时刻一起重试。
判断哪些情况重试、哪些不重试
retryable.sh——接收状态(500、429、400、timeout 等)作为参数,
输出 yes 或 no。
请求错误(400、422)和凭据问题(401、403)无论发送多少次结果都相同。429 可以重试,但必须遵守 Retry-After。
时间间隔会揭示原因
创建 payments.csv 并查找重复项。重复记录之间的时间间隔
会说明原因。
找出重复订单,并查看这些记录的时间差。若像 1 秒、2 秒、4 秒一样倍增,就是客户端重试。
总结
进行总结。
写明超时意味着什么、键以什么为单位生成,以及退避和抖动分别防止什么问题。