RocketMQ事务消息简单实现

1. 在父pom文件中引入依赖:

	<dependency>
	    <groupId>org.apache.rocketmq</groupId>
	    <artifactId>rocketmq-spring-boot-starter</artifactId>
	    <version>2.2.1</version>
	</dependency>

2.创建事务监听类 用于事务消息回调

package com.futurecloud.myTransactionListener;

/**
 * @author huangjialin
 * @date 2023/1/12
 * @description:
 */

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.futurecloud.dao.operation.AppPackageInfoDao;
import com.futurecloud.dao.sys.SysLogDao;
import com.futurecloud.entity.po.sys.SysLogEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * rocketmq 事务消息回调类
 */
@Slf4j
@Component
public class OrderTransactionListener implements TransactionListener {
    @Resource
    private SysLogDao sysLogDao;

    /**
     * half消息发送成功后回调此方法,执行本地事务
     *
     * @param message 回传的消息,利用transactionId即可获取到该消息的唯一Id
     * @param arg 调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
     * @return 返回事务状态,COMMIT:提交  ROLLBACK:回滚  UNKNOW:回调
     */
    @Override
    @Transactional
    public LocalTransactionState executeLocalTransaction(Message message, Object arg) {
        long startTime = System.currentTimeMillis();
        log.info("开始执行本地事务:订单信息:" + new String(message.getBody()));
        String msgKey = new String(message.getBody());
        SysLogEntity sysLogEntity = JSONObject.parseObject(msgKey, SysLogEntity.class);
        int saveResult;
        LocalTransactionState state;
        try {
            //修改为true时,模拟本地事务异常
//            boolean imitateException = true;
//            if(imitateException) {
//                throw new RuntimeException("更新本地事务时抛出异常");
//            }

            // 生成订单,本地事务的回滚依赖于DB的ACID特性,所以需要添加Transactional注解。当本地事务提交失败时,返回ROLLBACK_MESSAGE,则会回滚rocketMQ中的half message,保证分布式事务的一致性。
            sysLogEntity.setTime(System.currentTimeMillis()-startTime);
            saveResult = sysLogDao.insert(sysLogEntity);
            state = saveResult == 1 ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;

            // 更新本地事务并将事务号持久化,为后续的幂等做准备
            // TransactionDao.add(transactionId)
        } catch (Exception e){
            log.error("本地事务执行异常,异常信息:", e);
            state = LocalTransactionState.ROLLBACK_MESSAGE;
        }

        //修改为true时,模拟本地事务超时,对于超时的消息,rocketmq会调用 checkLocalTransaction 方法回查本地事务执行状况
//        boolean imitateTimeout = true;
//        if(imitateTimeout){
//            state = LocalTransactionState.UNKNOW;
//        }
        log.info("本地事务执行结果:msgKey=" + msgKey + ",execute state:" + state);
        return state;
    }


    /**
     * 回查本地事务接口
     * 当LocalTransactionState.UNKNOW 会进行回查本地事务接口
     * @param messageExt 通过获取 transactionId 来判断这条消息的本地事务执行状态
     * @return 返回事务状态,COMMIT:提交  ROLLBACK:回滚  UNKNOW:回调
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt){
        log.info("调用回查本地事务接口:msgKey=" +  new String(messageExt.getBody()));

        String msgKey = new String(messageExt.getBody());
        SysLogEntity sysLogEntity = JSONObject.parseObject(msgKey, SysLogEntity.class);

        // 备注:此处应使用唯一ID查询本地事务是否执行成功,唯一ID可以使用事务的 transactionId。但为了验证方便,只查询DB的订单表是否存在对应的记录
        // TransactionDao.isExistTx(transactionId)
        List<SysLogEntity> list = sysLogDao.selectList(new QueryWrapper<SysLogEntity>());

        LocalTransactionState state = list.size() > 0 ? LocalTransactionState.COMMIT_MESSAGE : LocalTransactionState.ROLLBACK_MESSAGE;
        log.info("调用回查本地事务接口的执行结果:" +  state);
        return state;
    }
}

3.客户端发送消息

    @RequestMapping("/test1")
    public boolean  test1(){
        String topic = "topic1";
        String tag = "reduce";
        SysLogEntity sysLogEntity = new SysLogEntity();
        sysLogEntity.setUsername("测试test3");
        // 设置监听器,此处如果使用MQ其他版本,可能导致强转异常
        ((TransactionMQProducer) rocketMQTemplate.getProducer()).setTransactionListener(orderTransactionListener);
        //构建消息体
        String msg = JSONObject.toJSONString(sysLogEntity);
        org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(msg).build();
        //发送事务消息,由消费者进行进行减少库存
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(topic + ":" + tag , message, null);
        logger.info("Send transaction msg result: " + sendResult);
        return sendResult.getSendStatus() == SendStatus.SEND_OK;
}

4.消费端消费

	  /**
 * 消费端
 *  topic
 *  consumerGroup 消费者组
 */
@Component
@RocketMQMessageListener(topic = MQConst.APPLY_AUDIT_FIRST_LOAN_TOPIC, consumerGroup = "huangjialin123")
public class ApplyAuditFirstLoanMqConsumer2 implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt){
        //消费消息
        String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        consumerCommonServer.applyAuditMqConsumer(message,businessType,1);

    }
}	

4.RocketMQ处理事务的局限性

1、Rocketmq考虑的是数据最终一致性。上游服务提交之后,下游服务最终只能成功,做不到回滚上游数据。
2、创建订单➕扣减库存,比如producer端是订单的创建,创建好发送消息到库存服务,库存扣减,但是库存为0扣减失败。这个时候RocketMQ是不支持数据TCC回滚的。针对这样的情况可以考虑使用阿里的Seata
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值