2.7 幂等与安全:契约的保证条款


2.7 幂等与安全:契约的保证条款

本节摘要:幂等与安全两张维度,是接口在"重试、队列、并发、重复提交"这些出事故时刻的兜底条款。本节用安全性与幂等性二维表,推导出 GET 可随意重试、PUT 与 DELETE 可安全重放、POST 需用幂等键防护的整套策略,并用一次"支付重试"的完整事故演练,展示保证条款如何在真实场景拦住资损。

学习目标

阅读完本节,你应当能够:

  1. 用安全、幂等两个维度给五种主方法做能力画像。
  2. 推导 GET、PUT、DELETE 的安全重放策略与 POST 的幂等键方案。
  3. 设计一个幂等键的生成与服务器去重流程。
  4. 识别并发下"读到旧值→改后覆盖"的丢失更新,并给出处理手段。

把两个维度焊成一张完整画像

第 2.3 节已经引入了安全与幂等两个词,这一节把它们升级成工程武器。安全与幂等不是一回事,合在一起才能算出一套完整能力画像:

  • 安全(Safe):不改状态,可以无限重复而无副作用。
  • 幂等(Idempotent):重复执行效果一致,重放安全。

把五个方法放回这两个维度,就得到一张"重试安全表",它直接回答"这方法能不能在不确定结果时重放"。

方法 重放会怎样 结论
GET 重复读结果一致 可放心重试
PUT 重复整体替换一致 可放心重试
DELETE 重复后资源仍不存在 可放心重试
PATCH 依赖补丁语义,可能不确定 谨慎
POST 重建一份,可能重复 需幂等键防护

把幂等键的去重流程画成一张图解,一图读懂中间产物:

02-07-fig01

图:幂等键去重流程

二、POST 的攻防:幂等键怎么救场

POST 天生不幂等,好事的场景是"这一次请求因为网络超时没收到响应,你猜它到底有没有成功到账"。直接重试可能造成重复扣款——这是最典型的资损事故入口。

解法是幂等键(Idempotency-Key)。流程拆开:

  1. 客户端在发关键 POST 前,生成一个唯一键(如 UUID),放进请求头。
  2. 服务器把"键 → 首次请求的处理结果"登记下来。
  3. 若同一个键的请求再次到达,服务器不做业务,直接返回首次的结果。
POST /payments HTTP/1.1 Host: api.example.com Idempotency-Key: op-77f1-4a2c {"orderId":456,"amount":12800,"method":"card"}

服务器对 op-77f1-4a2c 首次处理扣款,并缓存结果;网络抖动导致客户端重发同一键,服务器查到已处理,直接返回第一次的响应,不再扣第二次。这就是把不幂等的 POST,在重试维度上补成了"面对重复也安全"。

注意幂等键要与"业务幂等点"匹配:键应附着在同一笔业务(同一订单同一支付)上,到期清理策略也要想好,别让键字典无限膨胀。

落服务器端时还有两个细节别省。一是"登记的粒度":存的是键到"首次处理结果"的映射,重放时直接回该结果,而不是让业务再跑一遍——所以结果的快照要和键一起缓存,否则重放仍可能重复执行副作用。二是"并发重放的竞态":同一键的首个请求还没结束、第二个就进来了,服务器要保证"同键串行处理",通常用数据库唯一约束或分布式锁夹住,别让两次同时在业务里跑出双份扣款。这两处做到位,幂等键才是真保险而非花架子。

三、一次"支付重试"的完整演练

把上两个思想合进一次完整的资损防御演练:

背景:订单 456 待支付,用户点了"去支付"。

操作:客户端生成幂等键 op-77f1-4a2c,发 POST /payments 扣款 128 元。

结果:第一笔请求到服务器并成功扣款,返回 201,但响应在网络里丢了。客户端收到超时,无从判断成败。

两难:直接重试怕重复扣款;不重试怕用户明明付了款却显示未支付。

解读:客户端带着同一幂等键 op-77f1-4a2c 重发。服务器查到该键已处理,返回与最初一致的响应(含订单已支付信息)。用户看到已支付,系统语义一致,没有重复扣款。

变式:如果服务端未做幂等键保护,重发会再造一笔 128 元扣款,这是事故的典型面——幂等键就是为这类"响应不确定"的写操作上保险。

四、并发下的另一面:丢失更新

幂等管的是"同一请求重复出现",并发还要管"两个请求互相覆盖"。比如 A、B 同时改用户的余额字段:都读到余额 100,A 加 5 得 105,B 减 10 得 90 并写回,A 的 105 被覆盖回 90——A 的改动"丢了"。

应对手段按严重度递进:

  • 乐观锁:带上版本号或 If-Match 头,写时校验版本,变了就返回 409,客户端重试。
  • 条件更新:通过 ETag 进行 If-None-Match / If-Match,把"基于旧值的写"挡在服务器层。
  • 悲观锁 / 分布式事务:业务要求强一致时才上,复杂度最高。

丢失更新常见到你可能没察觉它在身边转。拿库存举例:两个仓管同时看到"剩余 50 件",一个要 +2 进货,一个要 -3 出库,两人都基于"50"操作,后写的人会把先写的人结果盖掉——这是经典的"读-改-写"竞态。字段上加版本号后,哪个操作落库先校验"版本还是上一个吗",一旦发现已被别人改过就返回 409,请发起方重新读取再决定要不要重试。层级的取舍很简单:像余额、库存这种"改错就资损"的字段用乐观锁守住;能容忍瞬时不一致的,交给兜底对账去补即可,不必事事都上分布式事务。

⚠️ 常见坑:把幂等键只加在 POST 上,却漏了"同一个写动作可能走不同方法"(如先 PUT 后 DELETE)的幂等边界。幂等是"业务动作"的属性,不是"方法"的标签,设计时要以业务为单位划定。

💡 关键直觉:安全与幂等,本质是"把网络的不确定性消化在服务器侧"。客户端可以放心地改、放心地重试,因为服务器承诺了"重来一次效果一样"——这份承诺,就是保证条款名叫"契约"的原因。

本节要点回顾

  • 要点一:GET、PUT、DELETE 天然幂等,可放心重放;POST 不幂等,需幂等键防护。
  • 要点二:幂等键流程是"客户端生成唯一键 → 服务器登记 → 重复键返回首果"。
  • 要点三:一次支付重试演练展示幂等键如何拦截重复扣款。
  • 要点四:并发丢失更新用乐观锁、ETag 条件更新、悲观锁按严重度处理。
  • 要点五:幂等以业务作为单位,不是方法标签,边界要按业务划定。
  • 要点六:保证条款把网络不确定性消化在服务器侧,换来客户端的放心重试。

四大构件的承重与保险都已就位。下一章进车间三,给这份契约加装版本号、查询面、错误、安全、缓存、批量六套加固件。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U