接口幂等性解决的是"同一个请求被执行多次,会不会造成重复业务效果"的问题。用户重复点击、网络重试、MQ 重复消费,都可能让同一业务被重复执行。
一句话概括:幂等就是多次调用和一次调用的业务结果一致;查询和按唯一值删除天然幂等,新增、支付、转账、发券这类写操作必须额外设计幂等。

GET 查询
DELETE 按唯一值删除
POST 新增或支付
PUT 增量更新
请求进入业务接口
是否天然幂等
直接处理
需要幂等设计
数据库唯一索引
Token + Redis
分布式锁
什么是幂等
幂等的定义是:多次调用方法或接口,不会改变业务状态,重复调用结果和单次调用结果一致。
常见需要幂等的场景:
- 用户重复点击提交按钮。
- 网络波动导致前端或网关重试。
- MQ 消息重复投递。
- 应用超时后触发重试机制。
- 支付、转账、下单、发券等写操作。
从 RESTful 看幂等性
| 请求方式 | 是否幂等 | 说明 |
|---|---|---|
| GET | 幂等 | 查询操作不改变状态 |
| POST | 通常不幂等 | 新增请求执行多次会插入多条数据 |
| PUT | 看写法 | 绝对值更新幂等,增量更新不幂等 |
| DELETE | 通常幂等 | 按唯一 ID 删除,多次删除结果一致 |
比如:
sql
update t_item set money = 500 where id = 1;
这是幂等的,因为执行多次结果都是 500。
sql
update t_item set money = money + 500 where id = 1;
这不是幂等的,因为执行多次会一直累加。
数据库唯一索引
新增类业务可以通过唯一索引防重复。
比如订单表有业务订单号 order_no,给它加唯一索引:
sql
create unique index uk_order_no on t_order(order_no);
同一个订单号重复插入时,数据库会拒绝第二次插入。
适合场景:
- 创建订单。
- 创建支付流水。
- MQ 消费记录。
- 发券记录。
唯一索引是最硬的兜底,优先级很高。
Token + Redis
Token 方案适合提交订单、转账、支付这类"前端先申请令牌,再带令牌提交"的场景。
存在
不存在
客户端请求 token
服务端生成唯一 token
token 存入 Redis
返回 token 给客户端
客户端携带 token
请求业务接口
Redis 中 token 是否存在
删除 token
执行业务
重复请求
直接返回
关键点是:验证 token 和删除 token 必须是原子操作。否则两个重复请求同时进来,都可能判断 token 存在。
实际项目里可以用 Redis Lua 脚本完成"判断 + 删除"。
示例脚本:
lua
local tokenKey = KEYS[1]
local exists = redis.call('exists', tokenKey)
if exists == 1 then
redis.call('del', tokenKey)
return 1
else
return 0
end
Java 侧伪代码可以这样理解:
java
Long result = redisTemplate.execute(script, List.of("submit:token:" + token));
if (result == 1) {
// token 存在且已删除,可以执行业务
createOrder();
} else {
// token 不存在,说明是重复提交或非法请求
return;
}
为什么要用 Lua?因为 Redis 执行 Lua 脚本是原子的,exists 和 del 不会被其他请求插队。这样两个重复请求同时到达时,只有一个请求能删除成功并继续执行业务。
分布式锁
分布式锁也能控制重复执行。
典型写法是用业务唯一值作为锁 key:
java
RLock lock = redissonClient.getLock("order:" + orderNo);
boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
try {
if (!locked) {
throw new RuntimeException("重复提交");
}
// 执行业务
} finally {
if (locked) {
lock.unlock();
}
}
使用分布式锁要注意:
- 锁粒度要小,最好按业务单号加锁。
- 抢不到锁要快速失败。
- 要保证 finally 释放锁。
- 高并发场景下性能比 Token 或唯一索引更重。
方案怎么选
| 方案 | 适合场景 | 优点 | 注意点 |
|---|---|---|---|
| 唯一索引 | 新增、流水、消费记录 | 简单可靠,数据库兜底 | 需要设计业务唯一键 |
| Token + Redis | 表单提交、支付、转账 | 性能好,体验清晰 | 校验和删除要原子 |
| 分布式锁 | 新增或修改都要串行 | 控制范围灵活 | 性能较低,锁粒度要小 |
面试回答模板
可以这样答:
幂等是指多次调用接口不会改变业务状态,重复调用和单次调用的结果一致。查询接口天然幂等,POST 新增、支付、转账、MQ 消费这类场景需要额外设计。新增数据可以用数据库唯一索引,比如订单号或消息 ID 做唯一约束。提交订单、支付等场景可以用 Token + Redis,第一次请求生成 token 存入 Redis,第二次提交时携带 token,服务端验证 token 存在后删除并执行业务,不存在就认为是重复请求。新增或修改也可以用分布式锁,但性能相对低,要控制锁粒度。
小结
幂等设计的核心不是"防重复提交"一句话,而是识别业务唯一性。
能用唯一索引兜底就先兜底;需要前置防重复就用 Token;需要串行化处理再考虑分布式锁。