一、什么是幂等(抽象定义)
幂等(Idempotent)
对同一资源的同一语义请求,
执行一次和执行多次,对系统最终状态的影响完全一致
⚠️ 注意:
- 不是“返回值一样”
- 是 “系统状态一样”
二、幂等问题的本质来源(所有系统通用)
| 根因 | 示例 |
|---|---|
| 网络重试 | 超时 / TCP 重传 / 网关重发 |
| 用户行为 | 连点按钮 / 刷新 |
| 系统重放 | MQ 重投 / 定时任务补偿 |
| 并发 | 多线程 / 多实例 |
| 分布式 | 服务超时 + 重试 |
| 外部系统 | 第三方回调 |
👉 只要“有副作用”,就一定要考虑幂等
三、幂等方案的「底层分类」(核心认知)
所有幂等方案,本质只分 4 大类:
1️⃣ 唯一性约束
2️⃣ 状态机约束
3️⃣ 请求去重
4️⃣ 操作原子性
下面逐类讲 所有可用方案
四、1️⃣ 唯一性约束类(最强幂等)
1.1 数据库唯一索引(终极兜底)
适用
- 强一致
- 并发高
- 插入型业务
示例
UNIQUE(order_no)
UNIQUE(biz_id + biz_type)
特点
✅ 并发 100% 安全
❌ 只能防“重复数据”
📌 结论
所有系统,只要能加唯一索引,一定要加
1.2 全局唯一业务 ID
常见
- requestId
- orderNo
- tradeNo
- messageId
UUID / 雪花 ID / 业务编码
用法
- 请求唯一
- 消息唯一
- 回调唯一
五、2️⃣ 状态机约束类(最常用)
2.1 状态前置校验(逻辑幂等)
示例
if (order.status != CREATED) {
return OK;
}
order.status = PAID;
适用
- 审批
- 流程
- 状态流转
📌 这是“业务层幂等”的核心
2.2 有限状态机(FSM)
CREATED → PAYING → PAID → SHIPPED → DONE
非法状态流转直接拒绝
2.3 乐观锁(版本号 / CAS)
UPDATE table
SET status = ?, version = version + 1
WHERE id = ? AND version = ?
- 0 行更新 = 已处理
六、3️⃣ 请求去重类(入口拦截)
3.1 Token 机制(一次性令牌)
流程
- 获取 token
- 提交携带 token
- token 校验 + 删除
📌 Web 表单 / 提交类接口最常用
3.2 请求指纹(Request Fingerprint)
hash(userId + params + uri)
SETNX(fingerprint)
场景
- 防止短时间重复请求
- 防刷
3.3 Redis SETNX(幂等锁)
SETNX(key, value, ttl)
特点
- 快速
- 分布式
- 有过期时间
⚠️ 不可单独作为最终兜底
七、4️⃣ 操作原子性类(分布式必备)
4.1 数据库事务
- 原子性
- 回滚
📌 单体系统幂等基础
4.2 本地消息表(事务消息)
业务操作 + 消息记录 同一事务
避免重复发送
4.3 MQ 消费幂等(必须掌握)
标准做法
messageId + 唯一索引
if (processed(messageId)) return;
process();
markDone(messageId);
4.4 Exactly-Once 的真相(重要)
❌ 现实中不存在真正 Exactly-Once
✅ At-Least-Once + 幂等 = Exactly-Once 效果
八、按「系统类型」给你完整方案矩阵
8.1 Web API
| 场景 | 方案 |
|---|---|
| 表单提交 | Token + Redis |
| 创建资源 | 唯一业务 ID |
| 更新状态 | 状态机 |
| 并发 | 乐观锁 |
8.2 分布式系统 / 微服务
| 场景 | 方案 |
|---|---|
| 服务重试 | requestId |
| RPC 调用 | 幂等接口设计 |
| 超时补偿 | 状态校验 |
8.3 MQ / 事件驱动
| 场景 | 方案 |
|---|---|
| 消费重复 | messageId |
| 顺序保证 | 分区有序 |
| 消息丢失 | 本地消息表 |
8.4 第三方回调 / Webhook
| 场景 | 方案 |
|---|---|
| 重复回调 | 回调 ID 唯一索引 |
| 延迟回调 | 状态判断 |
| 恶意调用 | 签名 + 幂等 |
8.5 定时任务 / 补偿任务
| 场景 | 方案 |
|---|---|
| 重跑 | 状态判断 |
| 并发执行 | 分布式锁 |
| 多实例 | 乐观锁 |
九、幂等设计的「黄金公式」
幂等 = 唯一标识 + 状态判断 + 原子操作 + 最终兜底
十、反模式(一定要避开)
❌ 只靠 Redis 锁
❌ 只靠前端限制
❌ 没有唯一索引
❌ 把异常当幂等
❌ 以为事务 = 幂等
十一、一句话总结(架构级)
幂等不是一个功能,而是系统面对不确定性的“免疫能力”