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

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

发送方

//创建消息发送者,同时设置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);
    }
});
@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返回消息成功,否则就是失败。失败没关系,会一直重试。当然也要加个逻辑,如果是库存没了,那也应该是成功。不然一直重试。

@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方法中已记录)
    }
}
RocketMQ事务消息是一种实现分布式事务的重要机制,它基于两阶段提交(2PC)和事务状态回查机制来确保消息发送与本地事务的最终一致性。通过事务消息,可以将消息发送与业务操作(如数据库更新)绑定在一个全局事务中,保证两者同时成功或同时失败[^2]。 ### RocketMQ 事务消息的核心流程 RocketMQ 事务消息的执行流程主要包括以下几个步骤: 1. **发送事务消息(Half Message)**: 生产者首先发送一条“半消息”(Half Message),该消息对消费者不可见。Broker 接收到半消息后,将其标记为“待提交”状态,并暂存该消息。 2. **执行本地事务**: Broker 确认收到半消息后,会通知生产者开始执行本地事务逻辑(如数据库操作)。生产者执行完成后,根据事务执行结果提交或回滚事务。 3. **提交或回滚事务**: 如果本地事务执行成功,生产者会向 Broker 提交事务,半消息将被标记为可消费状态;如果事务执行失败或发生异常,将回滚事务,Broker 会丢弃该消息。 4. **事务状态回查(Message Check)**: 如果 Broker 在一定时间内未收到事务提交或回滚的状态,会主动向生产者发起事务状态回查请求。生产者需返回事务的最终状态,以便 Broker 决定是否提交或丢弃该消息。这种机制确保了在异常情况下事务状态的最终一致性[^4]。 ### RocketMQ 事务消息的关键组件 - **Producer(生产者)**:负责发送事务消息并执行本地事务逻辑。 - **Broker(消息服务器)**:接收和暂存事务消息,并协调事务状态。 - **Consumer(消费者)**:仅消费已提交的事务消息。 ### RocketMQ 事务消息的代码示例 以下是一个使用 RocketMQ 发送事务消息Java 示例代码: ```java import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageExt; public class TransactionMessageProducer { public static void main(String[] args) throws MQClientException { TransactionMQProducer producer = new TransactionMQProducer("TransactionGroup"); producer.setNamesrvAddr("localhost:9876"); producer.setTransactionListener(new TransactionListener() { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { // 执行本地事务逻辑 try { // 模拟数据库操作 System.out.println("执行本地事务: " + new String(msg.getBody())); // 返回事务状态 return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { return LocalTransactionState.ROLLBACK_MESSAGE; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt msgExt) { // 事务状态回查逻辑 System.out.println("回查事务状态"); return LocalTransactionState.COMMIT_MESSAGE; } }); producer.start(); Message msg = new Message("TransactionTopic", "Hello Transaction".getBytes()); producer.sendMessageInTransaction(msg, null); } } ``` ### 事务消息的优势与适用场景 - **优势**: - 提供本地事务消息发送的强一致性保障。 - 支持事务状态回查,增强系统的容错能力。 - 适用于金融、订单、支付等对数据一致性要求较高的业务场景[^3]。 - **适用场景**: - 跨服务的数据一致性保障,如订单创建后需异步通知库存系统扣减库存- 业务操作与消息发送需要同步完成的场景。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值