请求幂等号和业务主键有什么区别?
简化版
请求幂等号标识“一次业务意图”,用于识别超时重试;业务主键标识“一个已经存在的业务实体”。两者生命周期和基数不同,不能默认共用。 同一幂等号必须绑定相同调用方、操作和参数摘要,并复用首次处理结果;一个订单主键上可以先后发生创建、支付、取消等多个幂等操作。
详细版
创建订单前可能还没有 orderId,客户端先生成 requestId,服务端以 tenant + operation + requestId 建唯一记录。首次请求执行并保存结果,重复请求返回同一结果;若同号参数不同,应拒绝冲突,而不是复用错误响应。
业务主键用于外键、查询和状态机,通常长期保留;幂等记录只需覆盖最大重试窗口,但资金操作可能需要更久审计。删除幂等记录后再次到达的旧请求有重复副作用风险,因此 TTL 要从客户端、MQ 和人工补偿的最长重放时间推导。
完整版教学
一、两个 ID 标识的对象不同
用户点两次“提交订单”可能是一次意图的网络重试,也可能是真的买两件。requestId 用来区分意图,orderId 用来标识最终订单。如果直接拿 userId 或商品 ID 幂等,会错误合并用户合法的第二次购买。
记忆钩子:幂等号认“这次动作”,业务主键认“动作作用的对象”。
二、创建类请求为什么尤其需要幂等号
Client --requestId=R1--> create order
Server creates order O9, response lost
Client --requestId=R1--> retry
Server returns existing O9, not O10
第二次请求到达时,客户端尚不知道 O9,因此不能靠 orderId 去重。服务端必须在创建实体前记录 R1 或用唯一约束将 R1 与 O9 原子绑定。
三、幂等键的作用域
| 字段 | 为什么需要 |
|---|---|
| tenant/user | 防不同调用方碰撞 |
| operation | 创建与取消不能互相去重 |
| requestId | 标识一次意图 |
| payloadHash | 防同号不同参数 |
唯一索引可设为 (tenant_id, operation, request_id)。只用 requestId 依赖所有客户端都生成全局唯一值,边界更脆弱。
四、处理记录的状态机
ABSENT -> PROCESSING -> SUCCEEDED
\-> FAILED_RETRYABLE
\-> FAILED_FINAL
首次请求通过唯一插入取得执行权。重复请求看到 SUCCEEDED 返回已保存结果;看到 PROCESSING 可等待、返回处理中或查询;最终失败是否允许同号重试要由接口契约定义。业务写和幂等记录最好在同一事务,或通过状态恢复消除中间空窗。
五、与业务状态机协作
订单 O9 可有 create:R1、pay:R2、cancel:R3 三个幂等动作。业务状态机还要验证 PAID 不能再次扣款、CANCELLED 不能发货。幂等去重挡相同请求,状态机挡语义上不合法的不同请求,两层都需要。
如果支付渠道也支持幂等键,应向下游传播稳定 R2,而不是每次重试生成新键,否则本地去重无法阻止下游重复扣款。
六、TTL 和安全清理
客户端最多重试 24 小时、MQ 最长保留 7 天、人工补偿可能 30 天,那么只保留 1 天幂等记录不安全。可长期保留幂等键到业务流水,或将旧记录压缩归档。删除前还要确认外部系统不会再次重放。
监控幂等命中、同号参数冲突、PROCESSING 超时、记录增长和下游重复拒绝。数据库唯一索引是并发竞态的最后防线,不能只用“先查再插”。
七、常见误区与追问
- 误区:有 orderId 就不需要 requestId。 创建之前客户端还没有业务主键。
- 误区:同一用户同一商品只能创建一次。 会误伤合法重复购买,幂等要按意图标识。
- 误区:收到重复请求直接返回成功即可。 应返回首次真实结果,参数冲突则拒绝。
- 误区:Redis SETNX 足以永久保证。 缓存过期或丢失后仍可能重复,核心写应有持久唯一约束。
- 追问:PROCESSING 卡死怎么办? 用租约、超时扫描和业务结果查询安全接管。
- 追问:参数摘要包含哪些字段? 包含影响副作用的规范化字段,排除 traceId 等非业务字段。
- 追问:幂等记录能否删除? 只能在所有可能重放窗口结束后按风险清理或归档。
八、加强记忆
请求幂等号围绕“一次意图”建立,业务主键围绕“一个实体”建立。落地时用调用方+操作+requestId 定作用域,payloadHash 防同号异参,状态机保存首次结果,唯一约束解决并发。TTL 则必须覆盖最长重放来源,而不是随便设一天。