接口幂等性设计实战|防止重复提交的几种方案
接口幂等性设计实战|防止重复提交的几种方案
用户点了两次提交按钮,订单创建了两个。
这种问题经常遇到,核心就是接口没做幂等。
一、什么是幂等性?
定义: 同一个操作执行多次,结果和执行一次一样。
幂等操作:
- 查询订单(查100次结果一样)
- 删除订单(删100次结果一样)
非幂等操作:
- 创建订单(执行100次创建100个订单)
- 扣减库存(执行100次扣100次库存)
二、常见场景
| 场景 | 原因 |
|---|---|
| 用户重复点击 | 按钮没禁用 |
| 网络超时重试 | 客户端自动重试 |
| 消息重复消费 | MQ消息重复投递 |
三、幂等性方案
3.1 Token机制(推荐)
原理:
- 进入页面时,后端生成Token返回前端
- 提交时带上Token
- 后端验证Token,成功后删除Token
- 重复提交时Token已删除,拒绝请求
// 1. 获取Token
@GetMapping("/token")
public String getToken() {
String token = UUID.randomUUID().toString();
redis.setex("submit_token:" + token, 300, "1");
return token;
}
// 2. 提交时验证
@PostMapping("/order")
public Result createOrder(@RequestHeader("X-Submit-Token") String token) {
String key = "submit_token:" + token;
Long deleted = redis.del(key);
if (deleted == 0) {
return Result.fail("请勿重复提交");
}
// 创建订单
return orderService.create(order);
}
3.2 唯一索引
-- 订单表加唯一索引
ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no);
public void createOrder(Order order) {
try {
orderMapper.insert(order);
} catch (DuplicateKeyException e) {
log.warn("订单已存在");
}
}
3.3 乐观锁
UPDATE orders
SET status = 'paid', version = version + 1
WHERE id = 1 AND version = 0;
3.4 状态机
public void payOrder(Long orderId) {
Order order = orderMapper.selectById(orderId);
if (order.getStatus() != OrderStatus.PENDING) {
return; // 幂等处理
}
// 更新状态(带状态条件)
int rows = orderMapper.updateStatus(orderId, OrderStatus.PAID, OrderStatus.PENDING);
if (rows == 0) {
return;
}
}
3.5 分布式锁
public Result createOrder(OrderDTO dto) {
String lockKey = "lock:order:" + dto.getUserId();
RLock lock = redisson.getLock(lockKey);
if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) {
return Result.fail("请勿重复提交");
}
try {
return doCreateOrder(dto);
} finally {
lock.unlock();
}
}
四、方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Token机制 | 表单提交 | 通用 | 需要前端配合 |
| 唯一索引 | 有唯一字段 | 简单 | 依赖数据库 |
| 乐观锁 | 更新操作 | 简单 | 只适合更新 |
| 状态机 | 状态流转 | 清晰 | 场景有限 |
| 分布式锁 | 复杂业务 | 通用 | 有性能损耗 |
五、分布式环境测试
幂等性问题往往在分布式环境才能复现,本地单机测不出来。
如果测试环境在公司内网,可以用组网工具把本地和测试环境连起来。我用的星空组网,把本地连到测试环境的Redis、数据库,模拟真实的分布式场景:
# 本地直连测试环境Redis
redis-cli -h 192.168.188.20 -p 6379
这样本地就能测分布式锁、Token机制这些逻辑了。
六、最佳实践
6.1 组合使用
1. 前端:按钮防重(loading)
2. 网关:请求去重(Token/RequestId)
3. 服务:业务幂等(唯一索引/状态机)
4. 数据库:唯一约束兜底
6.2 前端防重
async function submitOrder() {
if (this.submitting) return;
this.submitting = true;
try {
await api.createOrder(this.form);
} finally {
this.submitting = false;
}
}
总结
幂等性设计核心:
1. 识别幂等场景(创建、更新、删除)
2. 选择合适方案(Token、唯一索引、状态机...)
3. 多层防护(前端+后端+数据库)
4. 考虑异常情况(超时、重试)
记住:宁可多做检查,不可重复执行。
有问题评论区交流~

浙公网安备 33010602011771号