API
幂等
怎样安全地重试一次写、为什么重放不是一次新的裁决,以及重试买不到的那样东西。
两个 POST 接口都接受 Idempotency-Key 请求头。在裁决接口上,你应当总是带上它。
契约
| 属性 | 取值 |
|---|---|
| 请求头 | Idempotency-Key |
| 长度 | 8 到 255 字符。用 UUID。 |
| 作用域 | 你的账户。 |
| 保留 | 24 小时。 |
| 同键同体 | 重放已存储的响应,包含原来的 decision_id,且不再计费。 |
| 同键异体 | 409,type: .../idempotency-conflict。 |
被重放的响应带 Idempotency-Replayed: true。
怎么选键
从「这次请求关于什么」派生,而不是从「这是第几次尝试」派生。裁决一笔入金时,用从 deposit_ref 派生的键,意味着这笔入金的每一次重试——包括进程重启后、从另一个 worker 发出的重试——都收敛到同一条已存储的裁决上。
每次尝试换一个新 UUID 不是幂等。那是一种给自己重复计费、并让一笔入金留下两个 decision_id 的方式。
失败后重试
key := stable_key_for(deposit_ref)
attempt:
response := POST /v1/decide/deposit (Idempotency-Key: key)
if response.status == 200:
act_on(response.verdict)
else:
hold(deposit) # 不存在裁决
back_off_and_retry(attempt) # 同一个键,同一个请求体
复用键正是重试之所以安全的原因:如果第一次尝试其实已经到达我们这里并产生了裁决,只是连接断了,重试拿回的是同一条裁决,而不是又造了一条。
重试买不到什么
重试不能替代裁决。在你退避等待期间,这笔入金处于 HOLD。不要按计时器贷记,不要因为重试预算用完了就贷记,也不要把碰巧拿到的第一个 2xx 当成对你等待那段时间的追认。
409 意味着这个键已经被另一个请求体用过。不要用「换个新键,把改过的体发出去」来解决它——先搞清楚同一笔入金的请求体为什么变了。