前言

最近有看分布式的事务相关的内容。之前有配置过XA模式的分布式事务。然后又刚好看了有关rocketmq的最终一致性的相关方案。决定实践一下

逻辑实现

1、A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;

2、如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;

3、如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;

4、mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。

rocketMQTemplate设置消息key rocketmq 消息确认_回调方法

核心逻辑

添加TransactionListener的继承类,实现executeLocalTransaction(执行事务方法),和checkLocalTransaction(回调检查方法)。添加到TransactionMQProducer中。并且还要添加ExecutorService(用于产生回调执行的线程池)

代码实现

rocketmq监听回调类
用于配置事务执行和事务问题的回调

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 java.io.UnsupportedEncodingException;

@Slf4j
public class AffairsMsgListener implements TransactionListener {
    /**
     * COMMIT:提交  ROLLBACK:回滚  UNKNOW:回调
     *
     * @param msg
     * @param o
     * @return
     */
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object o) {
        log.info("回调方法,message:{}", msg);
        String msgBody;
        //执行本地业务的时候,再插入一条数据到事务表中,供checkLocalTransaction进行check使用,避免doBusinessCommit业务成功,但是未返回Commit
        try {
            msgBody = new String(msg.getBody(), "utf-8");
            String key = msg.getKeys();
            if (key.contains("fail")) {
                doBusinessfail(key, msgBody);
                return LocalTransactionState.ROLLBACK_MESSAGE;
            } else if (key.contains("unknown")) {
                doBusinessNuknow(key, msgBody);
                return LocalTransactionState.UNKNOW;
            } else {
                doBusinessCommit(key, msgBody);
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return LocalTransactionState.ROLLBACK_MESSAGE;
        } catch (Exception e) {
            e.printStackTrace();
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        log.info("检查方法,MessageExt:{}", msg);
        Boolean result = checkBusinessStatus(msg.getKeys());
        if (result) {
            return LocalTransactionState.COMMIT_MESSAGE;
        } else {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    public static void doBusinessCommit(String messageKey, String msgbody) {
        log.info("insert 事务消息到本地消息表中,消息执行成功,messageKey为:" + messageKey);
    }

    public static void doBusinessfail(String messageKey, String msgbody) {
        log.info("insert 事务消息到本地消息表中,消息执行失败!messageKey为:" + messageKey);
    }

    public static void doBusinessNuknow(String messageKey, String msgbody) {
        log.info("insert 事务消息到本地消息表中,消息执行无响应!messageKey为:" + messageKey);
    }

    public static Boolean checkBusinessStatus(String messageKey) {
        if (messageKey.contains("unknownSuccess")) {
            log.info("查询数据库 messageKey为" + messageKey + "的消息已经消费成功了,可以提交消息");
            return true;
        }
        if(messageKey.contains("unknownFail")) {
            log.info("查询数据库 messageKey为" + messageKey + "的消息不存在或者未消费成功了,可以回滚消息");
            return false;
        }
        return true;
    }

    public static void main(String[] args) {
        String a = "tru2e1";
        System.out.println(a.contains("true"));
    }
}

bean配置

@Bean(name = "producer2")
    public TransactionMQProducer getRocketMQProducer2() {
        TransactionMQProducer producer;
        producer = new TransactionMQProducer("groupName2");
        producer.setNamesrvAddr("namesrvAddr");
        //如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
        //producer.setInstanceName("instanceName");
        producer.setMaxMessageSize(4096);
        producer.setSendMsgTimeout(3000);
        //如果发送消息失败,设置重试次数,默认为2次
       producer.setRetryTimesWhenSendFailed(2);
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });
        producer.setExecutorService(executorService);
        producer.setTransactionListener(new AffairsMsgListener());
        try {
            producer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
        return producer;
    }

controller使用

@Resource(name = "producer2")
    private TransactionMQProducer transactionMQProducer;
    @Override
    public Result<String> sendOther(String key,String msgInfo) {
        // 可以不使用Config中的Group
        //transactionMQProducer.setProducerGroup(paramConfigService.rocketGroup2);
        SendResult sendResult = null;
        try {
            Message sendMsg = new Message(paramConfigService.rocketTopic2,
                    paramConfigService.rocketTag2,
                    key, msgInfo.getBytes());
            sendResult = transactionMQProducer.sendMessageInTransaction(sendMsg,null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("sendResult:{}", new Gson().toJson(sendResult));
        return Result.success(sendResult.getMsgId());
    }

结果值

正常事务执行完毕发送mq消息

2020-10-29 20:11:45.207  INFO 10057 --- [nio-8761-exec-3] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["test1","hello"]
2020-10-29 20:11:45.237  INFO 10057 --- [nio-8761-exec-3] com.chen.common.mq.AffairsMsgListener    : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=test1, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948774B70001, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948774B70001'}
2020-10-29 20:11:45.238  INFO 10057 --- [nio-8761-exec-3] com.chen.common.mq.AffairsMsgListener    : insert 事务消息到本地消息表中,消息执行成功,messageKey为:test1
2020-10-29 20:11:45.248  INFO 10057 --- [nio-8761-exec-3] c.c.c.service.impl.RocketMqServiceImpl   : sendResult:{"localTransactionState":"COMMIT_MESSAGE","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948774B70001","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":12},"queueOffset":15,"traceOn":true}
2020-10-29 20:11:45.251  INFO 10057 --- [nio-8761-exec-3] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948774B70001"},Time-Consuming:45ms
2020-10-29 20:11:45.304  INFO 10057 --- [MessageThread_1] com.chen.common.logAop.ParamsLogAspect   : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,RequestArgs:[[{"queueId":12,"storeSize":247,"queueOffset":3,"sysFlag":8,"bornTimestamp":1603973505207,"bornHost":{},"storeTimestamp":1603973505261,"storeHost":{},"msgId":"81D347B900002A9F000000000000ECED","commitLogOffset":60653,"bodyCRC":907060870,"reconsumeTimes":0,"preparedTransactionOffset":60390,"topic":"mytopic2","flag":0,"properties":{"MIN_OFFSET":"0","REAL_TOPIC":"mytopic2","MAX_OFFSET":"4","KEYS":"test1","TRAN_MSG":"true","CONSUME_START_TIME":"1603973505282","UNIQ_KEY":"C0A8041E274918B4AAC2948774B70001","WAIT":"true","PGROUP":"chengroup2","TAGS":"rocketTag2","REAL_QID":"12"},"body":[104,101,108,108,111],"transactionId":"C0A8041E274918B4AAC2948774B70001"}],{"messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":12},"delayLevelWhenNextConsume":0,"ackIndex":2147483647}]
2020-10-29 20:11:45.309  INFO 10057 --- [MessageThread_1] c.c.core.mqListener.RocketMsgListener    : 接受到的消息为:hello
2020-10-29 20:11:45.310  INFO 10057 --- [MessageThread_1] com.chen.common.logAop.ParamsLogAspect   : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,ResponseArgs:"CONSUME_SUCCESS",Time-Consuming:26ms

本地事务执行失败,消息不发送

2020-10-29 20:12:41.971  INFO 10057 --- [nio-8761-exec-4] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["fail","hello"]
2020-10-29 20:12:41.989  INFO 10057 --- [nio-8761-exec-4] com.chen.common.mq.AffairsMsgListener    : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=fail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948852730002, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948852730002'}
2020-10-29 20:12:41.989  INFO 10057 --- [nio-8761-exec-4] com.chen.common.mq.AffairsMsgListener    : insert 事务消息到本地消息表中,消息执行失败!messageKey为:fail
2020-10-29 20:12:41.992  INFO 10057 --- [nio-8761-exec-4] c.c.c.service.impl.RocketMqServiceImpl   : sendResult:{"localTransactionState":"ROLLBACK_MESSAGE","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948852730002","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":15},"queueOffset":16,"traceOn":true}
2020-10-29 20:12:41.992  INFO 10057 --- [nio-8761-exec-4] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948852730002"},Time-Consuming:21ms

本息事务执行条件未知,回调后发现事务成功

2020-10-29 20:13:27.162  INFO 10057 --- [nio-8761-exec-5] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["unknownSuccess","hello"]
2020-10-29 20:13:27.177  INFO 10057 --- [nio-8761-exec-5] com.chen.common.mq.AffairsMsgListener    : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=unknownSuccess, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948902FA0003, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948902FA0003'}
2020-10-29 20:13:27.177  INFO 10057 --- [nio-8761-exec-5] com.chen.common.mq.AffairsMsgListener    : insert 事务消息到本地消息表中,消息执行无响应!messageKey为:unknownSuccess
2020-10-29 20:13:27.178  INFO 10057 --- [nio-8761-exec-5] c.c.c.service.impl.RocketMqServiceImpl   : sendResult:{"localTransactionState":"UNKNOW","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948902FA0003","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":6},"queueOffset":17,"traceOn":true}
2020-10-29 20:13:27.178  INFO 10057 --- [nio-8761-exec-5] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948902FA0003"},Time-Consuming:17ms
2020-10-29 20:14:21.156  INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener    : 检查方法,MessageExt:MessageExt [queueId=6, storeSize=282, queueOffset=18, sysFlag=0, bornTimestamp=1603973607162, bornHost=/122.224.137.218:51509, storeTimestamp=1603973607171, storeHost=/129.211.71.185:10911, msgId=81D347B900002A9F000000000000F10B, commitLogOffset=61707, bodyCRC=907060870, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='mytopic2', flag=0, properties={REAL_TOPIC=mytopic2, TRANSACTION_CHECK_TIMES=1, KEYS=unknownSuccess, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948902FA0003, WAIT=false, PGROUP=chengroup2, TAGS=rocketTag2, REAL_QID=6}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948902FA0003'}]
2020-10-29 20:14:21.157  INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener    : 查询数据库 messageKey为unknownSuccess的消息已经消费成功了,可以提交消息
2020-10-29 20:14:21.189  INFO 10057 --- [MessageThread_2] com.chen.common.logAop.ParamsLogAspect   : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,RequestArgs:[[{"queueId":6,"storeSize":281,"queueOffset":4,"sysFlag":8,"bornTimestamp":1603973607162,"bornHost":{},"storeTimestamp":1603973661168,"storeHost":{},"msgId":"81D347B900002A9F000000000000F234","commitLogOffset":62004,"bodyCRC":907060870,"reconsumeTimes":0,"preparedTransactionOffset":61707,"topic":"mytopic2","flag":0,"properties":{"MIN_OFFSET":"0","REAL_TOPIC":"mytopic2","TRANSACTION_CHECK_TIMES":"1","MAX_OFFSET":"5","KEYS":"unknownSuccess","TRAN_MSG":"true","CONSUME_START_TIME":"1603973661187","UNIQ_KEY":"C0A8041E274918B4AAC2948902FA0003","WAIT":"true","PGROUP":"chengroup2","TAGS":"rocketTag2","REAL_QID":"6"},"body":[104,101,108,108,111],"transactionId":"C0A8041E274918B4AAC2948902FA0003"}],{"messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":6},"delayLevelWhenNextConsume":0,"ackIndex":2147483647}]
2020-10-29 20:14:21.190  INFO 10057 --- [MessageThread_2] c.c.core.mqListener.RocketMsgListener    : 接受到的消息为:hello
2020-10-29 20:14:21.190  INFO 10057 --- [MessageThread_2] com.chen.common.logAop.ParamsLogAspect   : ClassMethod:com.chen.core.mqListener.RocketMsgListener.consumeMessage,ResponseArgs:"CONSUME_SUCCESS",Time-Consuming:3ms

本息事务执行条件未知,回调后发现事务失败

2020-10-29 20:15:21.643  INFO 10057 --- [nio-8761-exec-8] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,RequestArgs:["unknownFail","hello"]
2020-10-29 20:15:21.653  INFO 10057 --- [nio-8761-exec-8] com.chen.common.mq.AffairsMsgListener    : 回调方法,message:Message{topic='mytopic2', flag=0, properties={KEYS=unknownFail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948AC22B0004, WAIT=true, PGROUP=chengroup2, TAGS=rocketTag2}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948AC22B0004'}
2020-10-29 20:15:21.654  INFO 10057 --- [nio-8761-exec-8] com.chen.common.mq.AffairsMsgListener    : insert 事务消息到本地消息表中,消息执行无响应!messageKey为:unknownFail
2020-10-29 20:15:21.654  INFO 10057 --- [nio-8761-exec-8] c.c.c.service.impl.RocketMqServiceImpl   : sendResult:{"localTransactionState":"UNKNOW","sendStatus":"SEND_OK","msgId":"C0A8041E274918B4AAC2948AC22B0004","messageQueue":{"topic":"mytopic2","brokerName":"broker-a","queueId":3},"queueOffset":19,"traceOn":true}
2020-10-29 20:15:21.654  INFO 10057 --- [nio-8761-exec-8] com.chen.common.logAop.ParamsLog2Aspect  : ClassMethod:com.chen.core.service.impl.RocketMqServiceImpl.sendOther,ResponseArgs:{"code":"200","msg":"成功","data":"C0A8041E274918B4AAC2948AC22B0004"},Time-Consuming:12ms
2020-10-29 20:16:21.207  INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener    : 检查方法,MessageExt:MessageExt [queueId=3, storeSize=279, queueOffset=20, sysFlag=0, bornTimestamp=1603973721643, bornHost=/122.224.137.218:51509, storeTimestamp=1603973721652, storeHost=/129.211.71.185:10911, msgId=81D347B900002A9F000000000000F4E2, commitLogOffset=62690, bodyCRC=907060870, reconsumeTimes=0, preparedTransactionOffset=0, toString()=Message{topic='mytopic2', flag=0, properties={REAL_TOPIC=mytopic2, TRANSACTION_CHECK_TIMES=1, KEYS=unknownFail, TRAN_MSG=true, UNIQ_KEY=C0A8041E274918B4AAC2948AC22B0004, WAIT=false, PGROUP=chengroup2, TAGS=rocketTag2, REAL_QID=3}, body=[104, 101, 108, 108, 111], transactionId='C0A8041E274918B4AAC2948AC22B0004'}]
2020-10-29 20:16:21.207  INFO 10057 --- [sg-check-thread] com.chen.common.mq.AffairsMsgListener    : 查询数据库 messageKey为unknownFail的消息不存在或者未消费成功了,可以回滚消息

说明

可以看到。当我们的本地事务成功或者回调后发现事务成功了。我们的rocketmq消费者都收到了对应的消息。当事务失败或者回调后发现事务失败了。都回滚了对应的消息。不会再发送对应的mq消息了。
以上就是我们的rocketmq事务相关的内容了。end~