欢迎关注微信公众号:互联网全栈架构
随着微服务的普及和落地,分布式事务就变成了一个绕不过去的坎,由于各个微服务操作的数据可能位于不同的数据库或者不同的机器上面,而这些操作要么全部成功,要么全部失败,所以就需要使用分布事务来进行处理。
比如用户在商城下单,就需要在订单服务中创建一个订单,同时在库存服务中扣减库存,而订单服务和库存服务往往连接的是不同的数据库,这就涉及到分布式事务了。
分布式事务相对复杂,而且也有多种解决方案,今天我们RocketMQ来实现分布式事务,达到最终一致性。文章模拟刚才提到的下单场景,提供尽可能全面的代码片段,供参考
RocketMQ提供了事务消息的功能,它大致分为如下几个阶段:
生产者发送half消息,half能够被broker接收,但不能被消费;
生产者发送half消息后,执行本地的数据库事务(比如生成订单数据);
根据本地事务的执行结果,生产者向broker发送“提交”或者“回滚”的请求;如果本地事务执行成功,则发送“提交”,half消息就变成了消费端可以接收的正式消息,否则,发送“回滚”,half消息就被删除,从而不会触发后续的数据库事务(比如扣减库存)。
接下来就是代码部分,主要使用Spring Boot+MyBatis+RocketMQ。首先引入依赖,如果需要详细的代码部分,也可以与我私信。然后配置yml,也是较为常规的做法,同时也需要定义一些实体类、DAO、mapper文件等。
然后是订单服务的代码:
@Service
@Slf4j
publicclass OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private OrderDao orderDao;
privatestaticfinal String TX_ORDER_TOPIC = "order_tx_topic";
privatestaticfinal String TX_DEDUCT_STOCK_TAG = "deduct_stock";
public String createOrder(OrderCreateDTO createDTO) {
String orderNo = generateOrderNo();
// 构造事务消息
OrderTxMessage txMessage = new OrderTxMessage(
orderNo,
createDTO.getProductId(),
createDTO.getQuantity()
);
// 发送事务消息
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
TX_ORDER_TOPIC + ":" + TX_DEDUCT_STOCK_TAG,
MessageBuilder.withPayload(txMessage)
.setHeader("order_no", orderNo)
.build(),
createDTO
);
log.info("Transaction message sent: {}", result.getMsgId());
return orderNo;
}
@Transactional
public boolean handleLocalTransaction(OrderCreateDTO createDTO, String orderNo) {
try {
// 1. 创建订单(初始状态为0-创建中)
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(createDTO.getUserId());
order.setProductId(createDTO.getProductId());
order.setQuantity(createDTO.getQuantity());
order.setAmount(createDTO.getAmount());
order.setStatus(0);
orderDao.insert(order);
// 2. 预留其他本地事务操作(如扣减账户余额等)
returntrue;
} catch (Exception e) {
log.error("Local transaction failed", e);
thrownew RuntimeException("订单创建失败");
}
}
public boolean checkLocalTransaction(String orderNo) {
Order order = orderDao.selectByOrderNo(orderNo);
return order != null && order.getStatus() != 0;
}
private String generateOrderNo() {
return"ORD" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000, 9999);
}
}
接下来以及事务消息的监听器,它会根据本地事务执行的情况,返回对应的状态。如果本地事务执行失败,或者在代码里直接返回ROLLBACK或者UNKNOWN,那么消息端就接收不到消息:
@Component
@RocketMQTransactionListener
@Slf4j
publicclass OrderTransactionListener implements RocketMQLocalTransactionListener {
@Autowired
private OrderService orderService;
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
MessageHeaders headers = msg.getHeaders();
String orderNo = headers.get("order_no", String.class);
OrderCreateDTO createDTO = (OrderCreateDTO) arg;
System.out.println("executeLocalTransaction:" + createDTO);
boolean success = orderService.handleLocalTransaction(createDTO, orderNo);
if (success) {
log.info("Local transaction committed: {}", orderNo);
return RocketMQLocalTransactionState.COMMIT;
}
return RocketMQLocalTransactionState.ROLLBACK;
} catch (Exception e) {
log.error("Transaction execution failed", e);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
MessageHeaders headers = msg.getHeaders();
String orderNo = headers.get("order_no", String.class);
System.out.println("checkLocalTransaction:" + JSONObject.toJSONString(headers));
try {
boolean exists = orderService.checkLocalTransaction(orderNo);
return exists ? RocketMQLocalTransactionState.COMMIT
: RocketMQLocalTransactionState.ROLLBACK;
} catch (Exception e) {
log.error("Transaction check failed", e);
return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
最后是库存的消费服务:
@Service
@RocketMQMessageListener(
topic = "order_tx_topic",
selectorExpression = "deduct_stock",
consumerGroup = "inventory_consumer_group",
consumeTimeout = 3000
)
@Slf4j
publicclass InventoryConsumer implements RocketMQListener<OrderTxMessage> {
@Autowired
private InventoryDao inventoryDao;
@Autowired
private OrderDao orderDao;
@Override
@Transactional
public void onMessage(OrderTxMessage message) {
String orderNo = message.getOrderNo();
// 幂等性检查:订单是否已经处理过
Order order = orderDao.selectByOrderNo(orderNo);
if (order == null) {
log.warn("Order not found: {}", orderNo);
return;
}
if (order.getStatus() == 2) {
log.info("Order already processed: {}", orderNo);
return;
}
// 获取当前库存信息
Inventory inventory = inventoryDao.selectByProductId(message.getProductId());
if (inventory == null) {
thrownew RuntimeException("商品不存在");
}
// 使用乐观锁扣减库存
int affectedRows = inventoryDao.deductStock(
message.getProductId(),
message.getQuantity()
);
if (affectedRows == 0) {
thrownew RuntimeException("库存扣减失败,请重试");
}
log.info("库存扣减成功,订单号:{}", orderNo);
}
}
可以使用以下的测试类来进行测试:
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
void testCreateOrder() {
OrderCreateDTO dto = new OrderCreateDTO();
dto.setUserId("U1001");
dto.setProductId("P2001");
dto.setQuantity(2);
dto.setAmount(new BigDecimal("199.99"));
String orderNo = orderService.createOrder(dto);
assertNotNull(orderNo);
}
}
通过这样的方式,达到了最终一致性的目的。当然,如果订单服务提交成功,然而库存服务还是提交失败,这时候就需要重试或者通过人工介入的方式来解决。
欢迎添加我的微信(请备注“互联网全栈架构”),我们一起在提升编程技术这条路上精进:

创作不易,烦请点个在看、点个赞。
有任何问题,也欢迎留言讨论。
推荐阅读:
1909

被折叠的 条评论
为什么被折叠?



