电商系统防重实战:三招解决订单重复创建难题
文章目录
- 一、重复订单引发的血泪教训
- 二、三大防重方案深度解析
- 2.1 方案一:Token令牌验证方案
- 2.1.1 实现原理
- 2.1.2 关键实现步骤
- 2.1.3 方案特点
- 2.2 方案二:分布式锁方案
- 2.2.1 锁键设计原则
- 2.2.2 Redisson实现示例
- 2.2.3 方案特点
- 2.3 方案三:Token+分布式锁双保险方案
- 2.3.1 双重校验流程
- 2.3.2 代码实现示例
- 2.3.3 方案特点
- 三、方案对比与选型建议
- 四、生产环境注意事项
一、重复订单引发的血泪教训
某电商大促期间,由于网络抖动导致用户重复点击下单按钮,产生大量重复订单,最终引发:
- 库存超卖导致订单无法履约
- 用户重复支付引发客诉
- 财务对账出现百万级差额
这些惨痛教训告诉我们:防重设计是电商系统的生命线!
二、三大防重方案深度解析
2.1 方案一:Token令牌验证方案
2.1.1 实现原理
客户端 服务端
|--获取Token请求--->| 生成Token存入Redis
|<--返回Token-----|
|--提交订单(Token)->| 校验Token是否有效
2.1.2 关键实现步骤
-
Token生成策略:
// 使用UUID+时间戳生成唯一标识 String token = UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
-
Redis存储设计:
// 存储结构示例 redisTemplate.opsForValue().set( "order:token:" + userId, token, 5, // 5秒过期时间 TimeUnit.SECONDS);
-
原子性校验逻辑:
@PostMapping("/createOrder") public Result createOrder(@RequestParam String token) { String redisKey = "order:token:" + getUserId(); // 使用Lua脚本保证原子性 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " + "then return redis.call('del', KEYS[1]) " + "else return 0 end"; Long result = redisTemplate.execute( new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(redisKey), token); if(result == 1L){ // 执行创建订单逻辑 return doCreateOrder(); } else { throw new BusinessException("请勿重复提交订单"); } }
2.1.3 方案特点
- 优点:实现简单、性能损耗小
- 缺点:依赖客户端传递Token
2.2 方案二:分布式锁方案
2.2.1 锁键设计原则
lock_key = "order_lock:{userId}:{businessType}"
// 示例:order_lock:12345:normal_order
2.2.2 Redisson实现示例
public Result createOrderWithLock(Long userId) {
String lockKey = "order_lock:" + userId + ":normal";
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待100ms,锁持有时间30秒
if(lock.tryLock(100, 30000, TimeUnit.MILLISECONDS)) {
return doCreateOrder();
} else {
throw new BusinessException("操作过于频繁,请稍后重试");
}
} finally {
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
2.2.3 方案特点
- 优点:实时性强、可靠性高
- 缺点:增加系统复杂度
2.3 方案三:Token+分布式锁双保险方案
2.3.1 双重校验流程
1. 前端获取Token
2. 提交订单时携带Token
3. 服务端先验证Token有效性
4. Token验证通过后获取分布式锁
5. 执行业务逻辑
2.3.2 代码实现示例
public Result createOrderWithDoubleCheck(String token) {
// 第一步:校验Token
String redisKey = "order:token:" + getUserId();
Long tokenValid = checkToken(redisKey, token);
if(tokenValid != 1L) {
throw new BusinessException("非法请求");
}
// 第二步:获取分布式锁
String lockKey = "order_lock:" + getUserId();
RLock lock = redissonClient.getLock(lockKey);
try {
if(lock.tryLock(50, 10000, TimeUnit.MILLISECONDS)) {
return doCreateOrder();
}
throw new BusinessException("系统繁忙,请稍后再试");
} finally {
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
2.3.3 方案特点
- 优点:双重保障、安全性最高
- 缺点:实现复杂度最高
三、方案对比与选型建议
维度 | Token方案 | 分布式锁方案 | 双保险方案 |
---|---|---|---|
实现复杂度 | ★☆☆ | ★★☆ | ★★★ |
安全性 | ★★☆ | ★★★ | ★★★ |
性能损耗 | 5-10ms | 15-30ms | 20-50ms |
适用场景 | 常规业务场景 | 高并发场景 | 资金敏感型业务 |
选型建议:
- 中小型系统优先选择Token方案
- 秒杀等高并发场景使用分布式锁
- 支付订单等关键业务使用双保险方案
四、生产环境注意事项
-
Token安全增强:
- 采用加密Token(如JWT)
-
分布式锁优化:
// Redisson看门狗机制自动续期 Config config = new Config(); config.setLockWatchdogTimeout(30000); // 默认30秒
-
异常处理三原则:
- 网络异常必须重试解锁
- 设置合理的锁超时时间
- 记录锁竞争监控日志