I3.05.2client-generated idempotency key设计

幂等标识需由客户端生成

别名: 幂等键 · Idempotency-Key · 请求指纹 · client nonce

概念解释

要把两次到达收成一次意图,双方必须共享一个事先存在的名字。这个名字叫幂等键(idempotency key),必须在客户端、在第一次发出之前生成,随后每一次重试原样带上。服务器按这个键查「做过没有」:做过就返回第一次的结果,没做过就做并记住。键若等到服务器生成,第一次应答丢失时客户端拿不到键,第二次只能再开一张新单。

客户端生成不是实现细节,是不确定窗口能被闭合的前提。界面防重点的是入口;键点的是意图的身份。

机制

网络只保证「至多努力送达」,不保证「恰好一次」。恰好一次要靠协议:发送方给意图命名,接收方以名为索引做去重。名字必须在进入不确定窗口之前就有——也就是按下去、写入本地队列的那一拍。此后杀进程、换网、重试,都是同一名字的再投递。服务器的表是「名 → 结果」:命中则短路,不命中则执行。

键若由服务器在响应里发放,命名发生在窗口的远端。窗口里第一次响应丢了,客户端处于「无名」状态,只能再要一个名,于是两个名对应一次意图,去重失败。键若从内容哈希来(金额+账号+时间戳到分钟),两笔合法的相同转账会被当成一笔,或一次改金额的重试会被当成新意图。所以键是客户端的随机或单调身份,绑定的是这一次用户动作,不是这一组字段值。

边界

只读 GET 用 URL 当键通常够了。真正的一次意图(支付、建单、发信、创建资源)才需要客户端键。服务端若用「同一用户五秒内相同正文」当去重,会误伤故意连发两条相同消息,也会漏掉五秒外的重试。键的生命周期要长过用户还可能重试的窗口(小时到天),太短则隔夜重试变新单;太长则存储膨胀,但膨胀是运维问题,不能靠缩短键寿命把重试变成两笔。多设备同时对同一业务点两下,是两个意图还是一个,要看产品:两台手机各自「支付」应是两个键;同一张已打开的支付页被系统恢复成两个 webview,应是一个键——键要跟意图的载体(那张页、那条本地队列项)走,不跟进程走。

怎么落地

  • 在用户按下的那一拍生成键,写入本地,再发请求。重试、刷新、进程恢复都读这把键,不新造。
  • 键随请求头或正文到达服务器;服务器以键为唯一约束,重复到达返回第一次的结果,而不是再执行。
  • 不要用「字段长得像」当键。金额被改过的重试是新意图,应有新键;同一次超时重试是旧意图,必须是旧键。
  • 验证:抓一次支付请求的键。故意丢弃响应,让客户端自动或手动重试。第二次请求必须带同一把键,服务端只落一笔记账。再清掉本地键模拟「错误地新造」:应出现两笔记账——用来证明键的生成时机一旦推到服务器或推到重试时,保护消失。

延伸

  • 同组I3.05.1 网络不确定时用户必然重试 · I3.05.3 界面防重不能替代服务端保护
  • 相邻H1.07 提交防重复 · I3.03 离线状态
  • 站内检索idempotency key · client-generated key · exactly-once

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/I3.05.2