멱등성 — 두 번 눌러도 한 번만 결제되게 · 재시도와 중복 · 讲解
재시도는 피할 수 없다
一句话总结
无法避免再次尝试。所以重复应该由服务器阻止。
为什么需要—客户无法区分
我发送了结算请求,但没有收到回复。两者中的哪个?
1. 请求没有到达服务器→需要重新发送
2. 到达后已经处理了,但是没有收到回复→再发送的话会被计费两次。
客户没有办法区分这个。所以不管选择哪一个都是错误的。
所以客户只能重新尝试,让服务器识别第二次。
横向性高度
客户端每次请求都会创建并粘贴唯一的键。
POST /payIdempotency-Key: 9f2c-...-a1{"user":"u1","amount":1000}重新尝试时使用相同的密钥。如果服务器见过该密钥,则不会处理,而是将保存的响应原封不动地返回。
核心是这个——第二次请求也收到成功响应。不是错误。从客户的角度来看,“请求一次就成功一次”,这是正确的。
存放在哪里
不能用记忆词典。理由有两个。
1. 重新开始的话就会忘记
2. 服务器有很多台——1号帕德记得的,2号帕德不知道。
2号更常见。用一个pad测试的话,会完美运行,但一旦附加自动缩放,就会出现重复。
所以存储必须是大家一起看的地方——DB或Redis。
如果同一高度有不同的正文的话
Idempotency-Key: k1 {"amount": 1000}Idempotency-Key: k1 {"amount": 99999}如果安静地通过第二个,将1000韩元的回复退还给99999韩元的请求。客户不知道99999韩元已经支付了。
因此,请求正文的哈希码一起保存,如果不同,则拒绝422。这是防止重复使用密钥的漏洞被悄悄掩盖的装置。
同时身高相同的话
这是最常出错的位置。
row = db.get(key) # 없다if not row: # ← 열 개가 동시에 여기를 통과한다 charge() db.put(key, response)在查询和插入之间,别人插手了。单纯的方式在负担加重的瞬间就会崩溃。
要做好,就要对关键字施加唯一限制,让插入失败的一方等待后读取存储的响应,或者将其锁定。使用数据库已经拥有的保障是最便宜的。
保存的回复要保留到什么时候?
不能永远在一起。一般24小时左右后删除。
太短的话,重复的延迟重试会造成重复,太长的话,缓存会一直增加。请将其设置为比客户端重试窗口宽裕。
遇到什么错误后再试一次
| 回复 | 重新尝试 | 为什么|
|---|---|---|
| 没有回复(超时)|✅|不知道是否到达了|
| 500,502,503 | ✅ | 服务器方面暂时问题|
| 429 | ✅(等了一会儿)|Retry-After遵守|
| 400,422 | ❌ | 请求错误。永远一样|
| 404 | ❌ | 一般是永久性的|
然后不断增加间隔(exponential backoff),并给每个客户端摇晃(jitter)。否则,试图恢复的服务器会在同一瞬间收到大量重试,再次崩溃。
附在什么请求上
附在金钱移动或制作某物请求上。POST,还有副作用的PATCH.
GET·PUT·DELETE本来是设计成排序的方法。PUT即使用同样的东西重复使用两次,结果也一样,DELETE第二个是“已经没有了”。就是设计是这样的,实现不会自动变成那样。
回复保存的回复时的陷阱
贴上横幅后,出现了新的问题。**把最初的回答原封不动地回复的
事情**并不总是正确的。
状态可能在这期间发生了变化。 创建订单后用户取消了,重新尝试
如果把来时的保存的201响应(“生成”)原封不动地还给客户,客户就会有有效的订单。
相信有。保存应该是请求的结果,客户端现在
如果需要了解状态的话,会在回复中一起提供查询路径。
如果把全部回复都保存的话,会变大。如果把整个正文都保存的话,存储器会很快用完。
需要的通常只有状态代码和创建资源的标识符,所以只包含那个。
剩下的会重新制作的。
设定保存期限后删除。永远不需要额外的键盘。客户的
比重新尝试窗口(一般24小时)要宽裕地保留,然后擦掉。如果不擦掉,表格就会
不断增长,索引越大,原本快速的查询就会变慢。
delete from idempotency_keys where created_at < now() - interval '7 days';决定如果过期的密钥再次出现该怎么办。 悄悄地重新处理的话会造成重复,
拒绝的话,非常晚的再尝试会失败。拒绝的一方是安全的,到时候
明确通知“此密钥已过期,请用新密钥重新发送”。
用回复头衔告知是否是重新尝试。Idempotency-Replayed: true相同的头衔
粘贴的话,客户和调查的人都会保存这个回答是否是新处理的。
我知道是不是。如果留到日志里的话,“请求增加了两倍”是重新尝试还是实际增加
马上就分开。
不信任密钥本身。因为是客户端创建的值,所以和其他用户的密钥和
可以重叠。必须与组织或用户识别符一起掌握唯一性才能获得别人的回复
没有接受过的事情。
在现场
重复结算的报告通常不是由用户发现的,而是由结算系统首先发现的。用户只记得“好像没支付,所以又按了一次”,而中间发生的事情只留在了服务器日志中。
所以,在发生错误后,连接到主机非常困难。因为已经离开的客户端不会发送密钥,所以服务器需要暂时同时接受有密钥和没有密钥的请求。如果不先确定如何处理没有密钥的请求——是否直接通过、拒绝、设定期限暂缓——那么在转移过程中会造成更大的混乱。
如果存在像移动应用程序一样无法强制部署的客户端,也会写下服务器根据请求正文的哈希值临时生成密钥,只在短时间内阻止重复的方法。虽然不是完全的,但会过滤掉用户连续两次点击的最常见情况。