接口幂等性设计实战|防止重复提交的几种方案

接口幂等性设计实战|防止重复提交的几种方案

用户点了两次提交按钮,订单创建了两个。

这种问题经常遇到,核心就是接口没做幂等。


一、什么是幂等性?

定义: 同一个操作执行多次,结果和执行一次一样。

复制代码
幂等操作:

- 查询订单(查100次结果一样)
- 删除订单(删100次结果一样)

非幂等操作:

- 创建订单(执行100次创建100个订单)
- 扣减库存(执行100次扣100次库存)

二、常见场景

场景 原因
用户重复点击 按钮没禁用
网络超时重试 客户端自动重试
消息重复消费 MQ消息重复投递

三、幂等性方案

3.1 Token机制(推荐)

原理:

  1. 进入页面时,后端生成Token返回前端
  2. 提交时带上Token
  3. 后端验证Token,成功后删除Token
  4. 重复提交时Token已删除,拒绝请求
java 复制代码
// 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 唯一索引

sql 复制代码
-- 订单表加唯一索引
ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no);
java 复制代码
public void createOrder(Order order) {
    try {
        orderMapper.insert(order);
    } catch (DuplicateKeyException e) {
        log.warn("订单已存在");
    }
}

3.3 乐观锁

sql 复制代码
UPDATE orders 
SET status = 'paid', version = version + 1 
WHERE id = 1 AND version = 0;

3.4 状态机

java 复制代码
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 分布式锁

java 复制代码
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 前端防重

javascript 复制代码
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. 考虑异常情况(超时、重试)

记住:宁可多做检查,不可重复执行。

有问题评论区交流~

复制代码
相关推荐
飞Link21 分钟前
告别 ROS 的臃肿:用 ZeroMQ 构建极速具身智能分布式大脑(附 Python 实战)
开发语言·分布式·python
会算数的⑨1 小时前
演进——从查日志到 AI 自治,企业监控体系的变迁
人工智能·分布式·后端·微服务·云原生
一叶飘零_sweeeet2 小时前
分布式权限体系破局:统一认证授权与 OAuth2.0 全链路架构落地实战
分布式·架构
014-code3 小时前
Dubbo 之 “最速传说”
java·分布式·dubbo
LF3_3 小时前
监听数据库binlog日志变化,将变动实时发送到kafka
数据库·分布式·mysql·kafka·binlog·debezium
闲猫3 小时前
企业级分布式系统运维全栈指南
分布式
渔民小镇3 小时前
告别 Redis/MQ —— ionet 分布式事件总线实战
java·服务器·分布式
智算菩萨3 小时前
与AI一起记忆:从分布式记忆到AI策划记忆与人机共忆——文献精读
论文阅读·人工智能·分布式·深度学习·ai·文献
珠海西格3 小时前
4 月 1 日起执行分布式光伏监控新规,直接影响从业者与项目收益
大数据·运维·服务器·分布式·能源
中议视控4 小时前
会议室和展厅分布式网络中控系统主机的选购思路
网络·分布式