幂等(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,通常就表示这一步已经被执行过了。
一个长任务例子
“生成报告”常见流程:
- 创建任务
- 扣减额度
- 生成内容
- 保存结果
- 通知用户
只要允许重试,这 5 步里至少前 1、2、5 步都必须幂等,否则很容易出现重复扣费和重复通知。
容易踩的坑
- 只做接口幂等,不做消息消费幂等
- 同一个幂等 key,不校验请求参数是否一致
- 只靠 Redis 做去重,没有数据库唯一约束兜底
总结
幂等不是接口小技巧,而是分布式系统的基础能力。
一句话记住:
允许请求重复到达,但不允许副作用重复生效。