RocketMQ事务消息实现订单创建 + 扣减库存

如果通过事务消息实现"订单创建 + 扣减库存"?

发送方

java 复制代码
//创建消息发送者,同时设置2个监听,一个是本地事务(在这里我们可以创建订单,根据订单的创建,我们返回成功和失败),一个是回查方法。
TransactionMQProducer producer = new TransactionMQProducer("ORDER_TRANSACTION_GROUP");
producer.setNamesrvAddr(nameServer);

// 设置事务监听器
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        return orderService.executeLocalTransaction(msg, arg); //executeLocalTransaction返回的结果,来决定消费者是否收到消息。
    }
    
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        return orderService.checkLocalTransaction(msg);
    }
});
java 复制代码
@Transactional
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    OrderCreateRequest request = (OrderCreateRequest) arg;
    
    try {
        // 1. 创建订单记录,状态为"待处理"
        Order order = new Order();
        order.setOrderId(request.getOrderId());
        order.setUserId(request.getUserId());
        order.setProductId(request.getProductId());
        order.setQuantity(request.getQuantity());
        order.setStatus(OrderStatus.PENDING.getCode()); // 初始状态
        order.setCreateTime(new Date());
        
        orderMapper.insert(order);
        
        // 2. 这里可以添加其他本地业务逻辑...
        
        // 本地事务执行成功,提交消息
        return LocalTransactionState.COMMIT_MESSAGE;
        
    } catch (Exception e) {
        // 本地事务执行失败,回滚消息
        return LocalTransactionState.ROLLBACK_MESSAGE;
    }
}


/**
 * 事务回查:Broker未收到事务状态时调用
 */
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
    try {
        // 解析消息中的订单ID
        String body = new String(msg.getBody(), StandardCharsets.UTF_8);
        Map<String, Object> messageBody = JSON.parseObject(body, Map.class);
        String orderId = (String) messageBody.get("orderId");
        
        // 查询订单状态
        Order order = orderMapper.selectByOrderId(orderId);
        
        if (order == null) {
            // 订单不存在,回滚消息
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        
        // 根据订单状态决定是否提交消息
        if (order.getStatus() == OrderStatus.PENDING.getCode()) {
            // 订单处于待处理状态,认为本地事务已提交
            return LocalTransactionState.COMMIT_MESSAGE;
        } else {
            // 订单已取消或其他状态,回滚消息
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        
    } catch (Exception e) {
        // 回查异常,返回未知状态,Broker会继续回查
        return LocalTransactionState.UNKNOW;
    }
}

RocketMQ 事务消息的执行流程如下:

当调用 transactionProducer.sendMessageInTransaction方法时,会依次执行以下操作:

发送半消息(这个是自动做的):首先向 Broker 发送一条"半消息",该消息对消费者不可见

执行本地事务:半消息发送成功后(只有发送成功才会进到这里),自动回调 executeLocalTransaction方法,在此方法中执行订单创建等本地业务逻辑

返回事务状态:根据本地事务执行结果,向 Broker 返回 COMMIT 或 ROLLBACK 状态

异常处理机制:
如果订单创建成功后,Broker 发生宕机导致无法接收状态确认,当 Broker 恢复后,会通过事务回查机制主动查询事务状态。此时在回查方法中,我们可以根据订单是否存在返回相应的事务状态,确保消息的最终一致性。

消费端

接下来就是库存扣减:扣减成功才给MQ返回消息成功,否则就是失败。失败没关系,会一直重试。当然也要加个逻辑,如果是库存没了,那也应该是成功。不然一直重试。

java 复制代码
@Service  
public class InventoryService {
    
    @Autowired
    private InventoryMapper inventoryMapper;
    
    /**
     * 消费订单创建消息,扣减库存
     */
    @RabbitListener(selector = "TAGS='CREATE_TAG'")
    public ConsumeConcurrentlyStatus consumeOrderCreateMessage(List<MessageExt> messages,
                                                             ConsumeConcurrentlyContext context) {
        for (MessageExt message : messages) {
            try {
                // 1. 解析消息
                String body = new String(message.getBody(), StandardCharsets.UTF_8);
                Map<String, Object> orderInfo = JSON.parseObject(body, Map.class);
                
                String orderId = (String) orderInfo.get("orderId");
                String productId = (String) orderInfo.get("productId");
                Integer quantity = (Integer) orderInfo.get("quantity");
                
                // 2. 幂等性检查:通过订单ID判断是否已处理过
                if (isMessageProcessed(orderId)) {
                    System.out.println("消息已处理,跳过。orderId: " + orderId);
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
                
                // 3. 执行库存扣减
                boolean success = deductInventory(productId, quantity, orderId);
                
                if (success) {
                    // 4. 记录消息处理状态,用于幂等性
                    recordMessageProcessed(orderId);
                    System.out.println("库存扣减成功,orderId: " + orderId);
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                } else {
                    // 库存不足,重试
                    System.out.println("库存扣减失败,等待重试。orderId: " + orderId);
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
                
            } catch (Exception e) {
                // 记录日志,返回重试
                System.err.println("处理消息异常: " + e.getMessage());
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
    
    /**
     * 扣减库存(保证幂等性)
     */
    @Transactional
    public boolean deductInventory(String productId, Integer quantity, String orderId) {
        // 检查是否已处理过该订单
        if (inventoryMapper.isOrderProcessed(orderId)) {
            return true;
        }
        
        // 检查库存是否充足
        Inventory inventory = inventoryMapper.selectByProductId(productId);
        if (inventory.getAvailableQuantity() < quantity) {
            return false;
        }
        
        // 扣减库存
        int rows = inventoryMapper.deductInventory(productId, quantity);
        if (rows == 0) {
            return false;
        }
        
        // 记录库存操作流水(用于幂等性检查和对账)
        InventoryOperation operation = new InventoryOperation();
        operation.setOrderId(orderId);
        operation.setProductId(productId);
        operation.setQuantity(quantity);
        operation.setType("DEDUCT");
        operation.setCreateTime(new Date());
        inventoryMapper.insertOperation(operation);
        
        return true;
    }
    
    private boolean isMessageProcessed(String orderId) {
        // 查询库存操作流水表,判断订单是否已处理
        return inventoryMapper.isOrderProcessed(orderId);
    }
    
    private void recordMessageProcessed(String orderId) {
        // 记录消息已处理(在deductInventory方法中已记录)
    }
}
相关推荐
BBB努力学习程序设计11 小时前
Java条件判断:程序的"决策大脑"
java
我是华为OD~HR~栗栗呀11 小时前
华为OD-C面经-23届学院哦
java·c++·python·华为od·华为·面试
小马爱打代码11 小时前
Spring AI:文生图:调用通义万相 AI 大模型
java·人工智能·spring
摇滚侠11 小时前
2025最新 SpringCloud 教程,网关功能、创建网关,笔记51、笔记52
java·笔记·spring cloud
又是忙碌的一天11 小时前
Socket学习
java·学习·socket
香吧香12 小时前
Spring boot 中 CommandLineRunner 在服务启动完成后自定义执行
java·spring boot·spring cloud
浓墨染彩霞12 小时前
Java-----多线路
java·经验分享·笔记
清晓粼溪12 小时前
SpringMVC02:扩展知识
java·后端·spring
曹牧12 小时前
Java String[] 数组的 contains
java·开发语言·windows
qq_124987075312 小时前
基于springboot+vue+mysql的校园博客系统(源码+论文+部署+安装)
java·vue.js·spring boot·mysql·毕业设计