idempotency-幂等

幂等(Idempotency)

幂等只解决一件事:

同一个请求执行 1 次和执行 N 次,最终结果一致。

flowchart LR
    A[请求到达] --> B{幂等记录是否存在}
    B -- 否 --> C[执行业务]
    C --> D[保存结果]
    D --> E[返回响应]
    B -- 是 --> F[直接返回首次结果]

先看两个例子

操作 是否幂等 原因
把用户状态设为 active 执行多次结果一样
给余额加 100 执行越多结果越偏

所以创建、扣费、发券、发通知这类操作,默认都不是幂等的。

为什么线上必须做幂等

重复执行非常常见:

  • 用户重复点击
  • 网关或 SDK 自动重试
  • 消息队列重复投递
  • Worker 成功后还没 ack 就崩了

如果没有幂等,最典型的问题就是:

  • 重复创建任务
  • 重复扣款
  • 重复发消息

最常见的 3 种做法

1. 幂等键

客户端提交一个业务唯一键:

POST /orders
Idempotency-Key: order_user42_20260414_001

服务端规则很简单:

  • 首次请求:执行业务并保存结果
  • 重复请求:直接返回第一次的结果
  • 同一个 key 但参数不同:拒绝

2. 数据库唯一约束

如果业务天然有唯一值,直接让数据库兜底最稳:

UNIQUE KEY uk_order_no (order_no)

适合:订单号、消息 ID、外部流水号。

3. 状态机条件更新

很多场景不是“防止重复插入”,而是“状态只能推进一次”:

UPDATE task
SET status = 'RUNNING'
WHERE task_id = 1001 AND status = 'PENDING';

影响行数为 0,通常就表示这一步已经被执行过了。

一个长任务例子

“生成报告”常见流程:

  1. 创建任务
  2. 扣减额度
  3. 生成内容
  4. 保存结果
  5. 通知用户

只要允许重试,这 5 步里至少前 1、2、5 步都必须幂等,否则很容易出现重复扣费和重复通知。

容易踩的坑

  • 只做接口幂等,不做消息消费幂等
  • 同一个幂等 key,不校验请求参数是否一致
  • 只靠 Redis 做去重,没有数据库唯一约束兜底

总结

幂等不是接口小技巧,而是分布式系统的基础能力。

一句话记住:

允许请求重复到达,但不允许副作用重复生效。

github