场景题:一笔订单,在取消的那一刻用户刚好付款了,怎么办?

这是一个典型的并发问题,涉及到订单状态和支付状态的冲突处理。在电商系统中,订单的取消和支付是两个关键操作,可能会在同一时间发生,尤其是在高并发场景下。以下是解决这个问题的几种常见方案:


1. 使用分布式锁

在订单取消和支付操作时,使用分布式锁(如Redis或Zookeeper)来确保同一订单在同一时间只能执行一个操作(取消或支付)。这样可以避免并发冲突。

   public boolean cancelOrder(String orderId) {
       String lockKey = "order_lock:" + orderId;
       String requestId = UUID.randomUUID().toString();
       try {
           // 尝试获取分布式锁
           boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
           if (!locked) {
               throw new RuntimeException("系统繁忙,请稍后再试");
           }

           // 检查订单状态
           Order order = orderRepository.findById(orderId);
           if (order.getStatus() == OrderStatus.PAID) {
               throw new RuntimeException("订单已支付,无法取消");
           }

           // 取消订单
           order.setStatus(OrderStatus.CANCELLED);
           orderRepository.save(order);
           return true;
       } finally {
           // 释放锁
           if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
               redisTemplate.delete(lockKey);
           }
       }
   }

支付操作也需要类似的锁机制。


2. 乐观锁机制

在数据库中为订单表添加一个版本号字段(version),每次更新订单时检查版本号是否一致。如果版本号不一致,说明订单已经被其他操作修改,需要回滚或重试。

   @Entity
   public class Order {
       @Id
       private String id;
       private String userId;
       private OrderStatus status;
       @Version
       private int version; // 乐观锁版本号
   }

   public boolean cancelOrder(String orderId) {
       Order order = orderRepository.findById(orderId);
       if (order.getStatus() == OrderStatus.PAID) {
           throw new RuntimeException("订单已支付,无法取消");
       }

       order.setStatus(OrderStatus.CANCELLED);
       try {
           orderRepository.save(order);
           return true;
       } catch (OptimisticLockingFailureException e) {
           throw new RuntimeException("订单状态已更新,请重试");
       }
   }

支付操作也需要类似的乐观锁检查。


3. 状态机设计

使用状态机(如State Machine)来管理订单的状态流转,确保订单状态只能按照预定义的规则进行变更。例如,订单从待支付状态只能转移到已支付已取消状态,而不能从已支付转移到已取消

   public enum OrderStatus {
       UNPAID, // 待支付
       PAID,   // 已支付
       CANCELLED // 已取消
   }

   public boolean cancelOrder(String orderId) {
       Order order = orderRepository.findById(orderId);
       if (order.getStatus() != OrderStatus.UNPAID) {
           throw new RuntimeException("订单状态不允许取消");
       }

       order.setStatus(OrderStatus.CANCELLED);
       orderRepository.save(order);
       return true;
   }

   public boolean payOrder(String orderId) {
       Order order = orderRepository.findById(orderId);
       if (order.getStatus() != OrderStatus.UNPAID) {
           throw new RuntimeException("订单状态不允许支付");
       }

       order.setStatus(OrderStatus.PAID);
       orderRepository.save(order);
       return true;
   }

通过状态机的设计,可以避免订单状态的不合法变更。


4. 补偿机制

如果订单在取消的同时支付成功,可以通过补偿机制来处理。例如:

  • 如果支付成功,但订单状态已经是已取消,则自动触发退款。
  • 如果订单取消成功,但支付已经完成,则自动触发退款。
   public boolean cancelOrder(String orderId) {
       Order order = orderRepository.findById(orderId);
       if (order.getStatus() == OrderStatus.PAID) {
           // 触发退款
           refundService.refund(orderId);
           throw new RuntimeException("订单已支付,已触发退款");
       }

       order.setStatus(OrderStatus.CANCELLED);
       orderRepository.save(order);
       return true;
   }

5. 消息队列异步处理

将订单取消和支付操作异步化,通过消息队列(如Kafka、RabbitMQ)来保证操作的顺序性。例如:

  • 用户发起取消或支付请求时,将操作发送到消息队列。
  • 消费者按照顺序处理消息,确保同一订单的操作是串行执行的。
   public void cancelOrderAsync(String orderId) {
       kafkaTemplate.send("order-operation-topic", new OrderOperation(orderId, "CANCEL"));
   }

   public void payOrderAsync(String orderId) {
       kafkaTemplate.send("order-operation-topic", new OrderOperation(orderId, "PAY"));
   }

   @KafkaListener(topics = "order-operation-topic")
   public void handleOrderOperation(OrderOperation operation) {
       if (operation.getAction().equals("CANCEL")) {
           cancelOrder(operation.getOrderId());
       } else if (operation.getAction().equals("PAY")) {
           payOrder(operation.getOrderId());
       }
   }

6. 数据库事务 + 状态检查

在数据库事务中,先检查订单状态,再进行状态变更。如果状态不符合预期,则回滚事务。

   @Transactional
   public boolean cancelOrder(String orderId) {
       Order order = orderRepository.findById(orderId);
       if (order.getStatus() != OrderStatus.UNPAID) {
           throw new RuntimeException("订单状态不允许取消");
       }

       order.setStatus(OrderStatus.CANCELLED);
       orderRepository.save(order);
       return true;
   }

支付操作也需要类似的检查和事务控制。


总结

解决订单取消和支付并发问题的核心思路是:

  1. 保证操作的原子性:通过分布式锁、乐观锁或数据库事务确保同一订单的取消和支付操作不会同时进行。
  2. 状态管理:使用状态机或状态检查,确保订单状态只能按照预定义的规则流转。
  3. 补偿机制:在冲突发生时,通过退款或其他方式补偿用户。
  4. 异步化处理:通过消息队列将操作异步化,避免直接并发冲突。

在实际开发中,可以根据业务场景选择合适的方案,或者结合多种方案来提高系统的健壮性。

THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容