订单重复提交是许多系统都会面临的常见问题,尤其是在电商等高并发场景中,如果没有合适的防重策略,可能会导致同一订单被处理多次,进而引发库存、支付等严重问题。你提到的 3 毫秒内订单重复提交的问题,涉及到一个非常短的时间窗口,要解决这个问题,可以采取一些简单但有效的防重策略,下面我们逐步讲解几种常见的方案。
1. 基于 Token 防重复提交
Token 防重是一个简单而有效的解决方案。核心思路是:每次用户提交订单时,服务器先生成一个唯一的 Token,发送给客户端。客户端在提交订单时必须携带这个 Token,服务器端会检查该 Token 是否已经使用过,如果已经使用则拒绝订单提交。
步骤:用户请求页面时,服务器生成一个唯一的 Token,并将该 Token 保存在 Redis 或数据库中,同时返回给客户端。客户端提交订单时,带上这个 Token。后台收到请求后,检查 Redis 或数据库中的 Token: 如果 Token 存在且未使用,则处理订单并将 Token 标记为已使用。如果 Token 已使用或不存在,则拒绝订单提交。优点:简单且高效,适用于表单提交或页面操作防重。缺点:需要服务器和客户端之间同步 Token 管理,增加了一些额外的管理逻辑。代码示例:
java
// 获取订单的唯一 Token
String token = redisService.getToken(orderId);
if (token == null || redisService.isTokenUsed(token)) {
throw new RuntimeException("订单已提交,不能重复提交");
}
// 订单处理逻辑...
redisService.markTokenAsUsed(token);
2. 基于数据库唯一约束
另一种比较稳妥的做法是利用数据库的唯一约束,确保订单数据不会重复插入。你可以在订单表中针对某些关键字段(比如订单号、用户 ID)设置唯一索引,这样在订单重复提交时,数据库会直接报错,防止同一订单被重复写入。步骤:在数据库订单表的订单号字段上添加唯一索引。当订单提交时,如果订单号重复,数据库会抛出唯一约束异常(Duplicate Key Exception),从而阻止订单的重复提交。优点:简单可靠,直接从数据库层面防止重复插入。无需额外的缓存或服务支持。缺点:每次订单提交都要访问数据库,性能可能受影响,特别是在高并发场景下。代码示例:
java
try {
// 插入订单到数据库
orderRepository.save(order)
} catch (DuplicateKeyException e) {
throw new RuntimeException("订单重复提交");
}
- 基于 Redis 的分布式锁
分布式锁是解决高并发重复提交的有效手段,尤其是在多实例部署的场景下。通过 Redis 实现的分布式锁,可以确保同一订单的提交在同一时刻只能处理一次。
步骤:
当用户提交订单时,后台通过 Redis 的 SETNX(set if not exists)命令尝试获取订单号对应的锁。
如果成功获取锁,处理订单并在处理完成后释放锁。
如果未能获取锁(意味着订单已在处理中),直接返回提示用户订单正在处理中,防止重复提交。
优点:
适用于分布式环境下的并发控制,性能高。
可以自定义锁的过期时间,防止锁遗留。
缺点:
需要引入 Redis,增加系统复杂度。
处理锁超时和故障恢复需要精心设计。
代码示例:
java
String lockKey = "order_lock_" + orderId;
boolean isLocked = redisService.tryLock(lockKey, 3000); // 3 秒过期时间
if (!isLocked) {
throw new RuntimeException("订单正在处理中,请稍后再试");
}
try {
// 处理订单逻辑...
} finally {
redisService.unlock(lockKey);
}
4. 基于接口幂等性
接口幂等性是指同样的请求,无论调用多少次,最终的结果都是一致的。我们可以通过设置一个唯一的标识符(例如订单号)确保即使多次提交,最终也只会执行一次。
步骤:
在请求中携带一个唯一的标识符(例如订单号)
。每次订单提交前检查这个标识符是否已经处理过,未处理则继续处理,否则返回重复提交提示。
优点:
逻辑简单,适用于任意提交请求的场景。保证每个订单请求只会执行一次。
缺点:
需要依赖数据库或缓存来保存已经处理过的请求标识符。
代码示例:
java
String orderId = request.getOrderId();
if (redisService.hasProcessed(orderId)) {
throw new RuntimeException("订单已提交,不能重复提交");
}
// 订单处理逻辑...
redisService.markAsProcessed(orderId);
5. 防重的组合策略
在实际项目中,防止订单重复提交往往需要结合多种策略来应对不同场景。比如可以使用 Redis 分布式锁 + 数据库唯一约束 组合使用,既能提高并发下的性能,又有数据库层面的强一致性保障。组合示例:使用 Redis 分布式锁确保在高并发情况下,只有一个线程处理订单。数据库层面设置唯一约束,双重防止订单重复提交。
**
java
String lockKey = "order_lock_" + orderId;
boolean isLocked = redisService.tryLock(lockKey, 3000); // 获取分布式锁
if (!isLocked) {
throw new RuntimeException("订单正在处理中");
}
try {
// 处理订单并插入数据库(带有唯一约束)
orderRepository.save(order);
} catch (DuplicateKeyException e) {
throw new RuntimeException("订单重复提交");
} finally {
redisService.unlock(lockKey);
}
**
总结对于如何防止 3 毫秒内订单重复提交,常见的策略有以下几种:
Token 防重提交 :通过唯一 Token 识别每次订单提交。
数据库唯一约束 :从数据库层面杜绝重复订单。
Redis 分布式锁 :高并发场景下,利用分布式锁保证同一时刻只有一个订单被处理。
接口幂等性 :确保重复提交的请求不会重复执行。
组合策略 :结合 Redis 锁和数据库唯一约束,实现更高的性能和一致性。
在实际项目中,针对不同的场景选择合适的方案,可能还需要结合缓存、消息队列等技术手段,最终保障系统的稳定性和正确性。