SpringBoot集成RockerMq实现分布式事务

RockerMq分布式事务

至于什么是分布式事务,以及常见的分布式事务解决方案,请查看我的另一篇文章《分布式事务组件Seata入门》,这里我们主要介绍下用消息中间件RockerMq怎么解决分布式事务的。

RockerMq分布式事务原理

RocketMQ的Producer提供了三种模式,NormalProducer(普通)、OrderProducer(顺序)、TransactionProducer(事务),通过名称可见。RocketMQ主要是通过TransactionProducer来实现分布式事务的。
在这里插入图片描述
1、A服务先发送个Half Message给Broker,消息中携带 B服务 即将要+100元的信息。
2、当A服务知道Half Message发送成功后,那么开始第3步执行本地事务。
3、执行本地事务(会有三种情况1、执行成功。2、执行失败。3、网络等原因导致没有响应)
4.1)、如果本地事务成功,那么Product像Brock服务器发送Commit,这样B服务就可以消费该message。
4.2)、如果本地事务失败,那么Product像Brock服务器发送Rollback,那么就会直接删除上面这条半消息。
4.3)、如果因为网络等原因迟迟没有返回失败还是成功,那么会执行RocketMQ的回调接口,来进行A服务事务的回查。

RockerMq分布式事务实战

  1. 第一步:生产者发送事务消息(HALF MESSAGE )
    这里HALF MESSAGE到MQ服务器之后,主题会被替换为RMQ_SYS_TRANS_HALF_TOPIC,并且会调用MessageStore#putMessage方法将消息持久化。所以此时HALF MESSAGE并不能被消费者消费。
//将要发送的对象转换成消息需要的json字符串格式
JSONObject jsonObject = new JSONObject();
ProductOrderNoLog productOrderNoLog = new ProductOrderNoLog();
String orderNo = UUID.randomUUID().toString().replaceAll("-","");
productOrderNoLog.setOrderNo(orderNo);
productOrderNoLog.setProductId(product.getId());
productOrderNoLog.setTotalAmount(1);
productOrderNoLog.setAmount(product.getPrice());
productOrderNoLog.setAccountId(1);
jsonObject.put("productOrder", productOrderNoLog);
Message<String> message = MessageBuilder.withPayload(jsonObject.toString()).build();
rocketMQTemplate.sendMessageInTransaction("produce_pay_group", "producer_pay_tag",message,null);
log.info("======消息事务发送成功======");
  1. 第二步:生产者实现RocketMQLocalTransactionListener接口
    实现executeLocalTransaction和checkLocalTransaction方法,在第一步的HALF MESSAGE被发送成功,会回调executeLocalTransaction,执行本地事务。
    • 本地事务执行成功,返回COMMIT,MQ服务器会把HALF MESSAGE塞到真实的队列中,并同时向RMQ_SYS_TRANS_OP_HALF_TOPIC塞一条消息。此时消费端可以消费消息。
    • 本地事务执行失败,返回ROLLBACK,表示消息需要回滚,会直接删掉HALF MESSAGE,并并同时向RMQ_SYS_TRANS_OP_HALF_TOPIC塞一条消息。
    • 本地事务返回未知,返回UN_KNOW,不做任何处理。MQ服务器会通过TransactionalMessageCheckService定时调用checkLocalTransaction检查本地事务。(最多重试15次,超过了默认丢弃此消息)
/**
 * 步骤二:
 * 描述:mq收到事务消息后,开始执行本地事务
 * 1:修改商品库存
 * 2:保存订单商品扣除记录
 * @param message 消息体
 * @param o
 * @return 事务消息进行回滚还是提交
 */
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
    log.info("executeLocalTransaction");
    try {
        String jsonString = new String((byte[])message.getPayload());
        JSONObject jsonObject = JSONObject.parseObject(jsonString);
        ProductOrderNoLog productOrderNoLog = JSONObject.parseObject(jsonObject.getString("productOrder"), ProductOrderNoLog.class);
        Boolean result = productService.commitProduct(productOrderNoLog);
        if (result) {
            log.info("executeLocalTransaction COMMIT");
            return RocketMQLocalTransactionState.UNKNOWN;
        }else{
            log.info("executeLocalTransaction UNKNOWN");
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    } catch (Exception e){
        log.info("executeLocalTransaction Exception UNKNOWN");
        return RocketMQLocalTransactionState.UNKNOWN;
    }
}
/**
 * 步骤三
 * 描述:mq回调检查本地事务执行情况(查询订单商品扣除记录是否存在)
 * @param message 消息体(保存里订单商品扣除)
 * @return 事务消息进行回滚还是提交
 */
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
    log.info("checkLocalTransaction");
    String jsonString = new String((byte[])message.getPayload());
    JSONObject jsonObject = JSONObject.parseObject(jsonString);
    ProductOrderNoLog productOrder = JSONObject.parseObject(jsonObject.getString("productOrder"), ProductOrderNoLog.class);
    ProductOrderNoLog productOrderNoLog = productService.getByOrderNo(productOrder.getOrderNo());
    if(null == productOrderNoLog){
        log.info("checkLocalTransaction UNKNOWN");
        return RocketMQLocalTransactionState.ROLLBACK;
    }
    log.info("checkLocalTransaction COMMIT");
    return RocketMQLocalTransactionState.COMMIT;
}
  1. 第三步:消费端编写consumer监听器
    注意:只有在第二步MQ服务收到生产者本地事务返回COMMIT,或者检测到生产者本地事务提交成功。消费端才会收到消息。
    此处如果消费者失败并不会回滚生产者,而是通过重试,直到消费者成功。
/**
* 功能:mq消息监听器
* 描述:监听来自producer_pay_tag的topic的消息,此topic要与生产者发送的topic一致
*/
@Slf4j
@Component
@RocketMQMessageListener(topic = "producer_pay_tag",consumerGroup = "order_consumer_group")
public class ConsumerListener implements RocketMQListener<String> {

   @Autowired
   private AccountService accountService;

   @Autowired
   private OrderService orderService;

   @Override
   public void onMessage(String s) {
       JSONObject jsonObject = JSONObject.parseObject(s);
       ProductOrderNoLog productOrderNoLog = JSONObject.parseObject(jsonObject.getString("productOrder"), ProductOrderNoLog.class);
       String accountResult = accountService.buyProduct(productOrderNoLog);
       if(null != accountResult && !"".equals(accountResult) && "success".equals(accountResult)){
           String orderResult = orderService.buyProduct(productOrderNoLog);
           if(null != orderResult && !"".equals(orderResult) && "success".equals(orderResult)){
               log.info("订单完成");
           }else{
               log.error("order异常");
           }
       }else{
           log.error("account异常");
       }
   }
}

源码:
注:如有疑问请在下方留言
spring-rocket源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值