如果通过事务消息实现"订单创建 + 扣减库存"?
发送方
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方法中已记录)
}
}