当通过调用创建实例接口在负载均衡中创建云服务器时,如果遇到了请求超时或服务器内部错误时,客户端可能会尝试重发请求,这时客户端可以通过提供可选参数 ClientToken 避免服务器创建出比预期要多的实例,也就是通过提供 ClientToken 参数保证请求的幂等性。ClientToken 是一个由客户端生成的唯一的、大小写敏感、不超过 64 个 ASCII 字符的字符串。

如果用户使用同一个 ClientToken 值调用创建实例接口,则服务端会返回相同的请求结果,包含相同的 InstanceId。因此用户在遇到错误进行重试的时候,可以通过提供相同的 ClientToken 值,来确保负载均衡只创建一个实例,并得到这个实例的 InstanceId。

如果用户提供了一个已经使用过的 ClientToken,但其他请求参数不同,则负载均衡会返回 IdempotentParameterMismatch 的错误代码。但需要注意的是,SignatureNonce、Timestamp 和 Signature 参数在重试时是需要变化的,因为负载均衡使用 SignatureNonce 来防止重放攻击,使用 Timestamp 来标记每次请求时间,所以再次请求必须提供不同的 SignatureNonce 和 Timestamp 参数值,这同时也会导致 Signature 值的变化。

通常,客户端只需要在 500(InternetError)或 503(ServiceUnavailable)错误、或者无法得到响应结果的情况下进行重试操作。返回结果是 200 时,重试可以得到上次相同的结果,但不会对服务端状态带来任何影响。而对 4xx 的返回错误,通常重试也是不能成功的。

云厂商的 ClientToken 方案是幂等设计的范本,但落地到自己系统时需要一套完整工具箱:唯一索引、token 机制、状态机、乐观锁、去重表各有适用面。本文按方案谱系展开,并给出选型决策与重试侧的配合规范。

为什么需要幂等

重试无处不在:客户端超时重试、MQ 重复投递、网关 failover、用户双击提交。非幂等接口 + 重试 = 重复扣款/重复下单/重复发消息。幂等性定义:同一请求执行一次与多次,对系统状态的影响相同。

方案谱系

1. 数据库唯一索引(最硬兜底)

ALTER TABLE orders ADD UNIQUE KEY uk_req (request_id);
-- 重复插入直接失败,业务捕获后返回首次结果

适用:有天然唯一业务键(订单号、流水号)。优点:绝对可靠、无额外组件;缺点:只防”插入型”重复,更新型无效。

2. Token 机制(防重复提交)

  • 进入表单页时服务端发 token(存 Redis,setnx + 过期)
  • 提交时带 token,服务端原子删除(DEL 返回 1 才放行)→ 第二次提交删除失败即拒绝
  • 适用:前端重复提交、防双击;注意 token 删除与业务执行的事务边界(先删 token 再执行业务,失败不回补则用户需刷新——或业务成功后再异步清理)

3. 状态机前置校验(更新型幂等)

UPDATE orders SET status='PAID', pay_no=? WHERE id=? AND status='UNPAID';
-- 影响行数 0 = 已处理过,直接返回成功(而非报错)

适用:支付回调、MQ 消费等”状态流转”场景。关键细节:重复请求应返回首次的成功结果(查询返回),而不是报”状态不对”——幂等的语义是”多次调用如同一次”。

4. 去重表/幂等表(无天然唯一键时)

独立表记录 (biz_type, biz_key, result),与业务表同事务写入;消费前先查/插去重表。本质是把唯一索引方案显式化。

5. 乐观锁版本号:version 列校验(见乐观锁篇),防”并发重复更新”而非”重复请求”,与上述方案互补。

6. 分布式锁:setnx 锁 biz_key 串行化重复请求;注意锁只防并发不防”锁过期后的迟到重试”,需与去重表组合。

选型决策表

场景 首选 兜底
创建型(下单/注册) 唯一索引/去重表 —
前端重复提交 Token 机制 唯一索引
支付/回调/MQ 消费 状态机前置 + 去重表 唯一索引
并发更新 乐观锁/行锁 状态机
跨服务编排 幂等键透传(全链路同一 key) 各节点去重表

重试侧的配合规范(幂等的另一半)

  • 只重试可重试错误:超时/5xx/连接失败;4xx 不重试(ClientToken 范例同理)
  • 重试携带同一幂等键:键随”业务意图”生成(一次下单意图一个键),而非随每次 HTTP 请求生成
  • 重试带退避与上限:避免重试风暴
  • MQ 消费:消费成功才 ack;消费逻辑幂等(去重表/状态机),因为 broker 重投不可避免

幂等键设计要点

  • 全局唯一 + 业务语义稳定:客户端生成 UUID 或”用户+业务单号”哈希;同一意图重试键不变
  • 存储带 TTL:去重记录不必永久(按业务追溯期定,如 7 天)
  • 参数一致性校验:同键不同参数报 Mismatch(云厂商范例),防止键被误复用

幂等性不是单个技巧,而是”请求侧带稳定幂等键 + 服务侧原子去重 + 重复请求返回首次结果“的三段式设计;唯一索引与状态机是其中最可靠的两块基石,token 与锁只是特定入口的补充。把这三段做全,重试、重投、双击才都不再是威胁。