消息中间件RocketMQ事务消息

本文介绍Apache RocketMQ如何通过2PC思想实现事务消息处理,并通过补偿机制确保消息的一致性。文章详细展示了事务消息的实现过程及示例代码。
概述

RocketMQ和其他消息中间件最大的一个区别是支持了事务消息,这也是分布式事务里面的基于消息的最终一致性方案。

Apache RocketMQ在4.3.0版中已经支持分布式事务消息,这里RocketMQ采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。

在这里插入图片描述
流程如上图所示:

  1. 生产者向broker发送消息,此时发送的消息对consumer是不可见状态,也就是发送后,consumer不能消费该消息。
  2. broker向生产者发送确认消息。
  3. 执行本地事务,比如注册用户。
  4. 根据本地事务的执行情况选择提交或者回滚消息。

补偿:

  1. 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”。
  2. Producer收到回查消息,检查回查消息对应的本地事务的状态
  3. 根据本地事务状态,重新Commit或者Rollback。
    其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。也就是第四步由于某些原因,比如宕机等,导致第四步没有执行。这个补偿机制是由同一生产者组的其他可用生产者来完成。

需要两个角色:

  • 事务消息消息生产者
  • TransactionListener 实现该接口,定义本地事务逻辑以及回查逻辑。
    例子:

流程如下,我只是把本地事务逻辑修改了。
在这里插入图片描述

public class LocalTransactionException extends Exception {
}

public class TransactionListenerDemo implements TransactionListener{


    private AtomicInteger num = new AtomicInteger();

    //这个是记录本地事务执行结果为UNKNOW的情况下的状况用于回查,实际上要用数据库表来记录,这里就简单点用一个map来记录。
    private Map<String,Boolean> checkMap = new ConcurrentHashMap<>();

    //执行本地事务的地方,返回本地事务执行结果,有三种状态
    //COMMIT_MESSAGE  执行成功,提交消息
    //ROLLBACK_MESSAGE 执行失败,回滚消息
    //UNKNOW  中间状态,该状态的消息要进行回查
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        //这里模拟业务操作,value对4取模,0就返回COMMIT_MESSAGE 1 就抛出异常
        // 2就返回UNKNOW(如果是2,就往回查表里输入true,表示回查提交,如果是3或者null,那就回查回滚。
        try {
            //这里就全包在try/catch里面了  实际上只需包会本地事务代码,比如注册用户的sql操作。
            int value = num.getAndIncrement();
            int mod = value%4;
            if (mod == 0){
                return LocalTransactionState.COMMIT_MESSAGE;
            }else if (mod == 1){
                //模拟抛出异常
                throw new LocalTransactionException();
            }else {
                if (mod == 2){
                    //如果为2,就回查时让其提交
                    checkMap.put(msg.getTransactionId(),true);
                }else{
                    //否则消息回滚
                    checkMap.put(msg.getTransactionId(),true);
                }
                return LocalTransactionState.UNKNOW;
            }
        }catch (LocalTransactionException ex){
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }



    }

    @Override
    //回查方法,本地事务返回UNKNOW会触发。
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        Boolean commitOrNot = checkMap.get(msg.getTransactionId());
        if (commitOrNot != null && commitOrNot){
            //如果commitOrNot不为空并且为true,提交消息
            return LocalTransactionState.COMMIT_MESSAGE;
        }else {
            //否则回滚消息,为空表示checkMap.put(); 操作和return LocalTransactionState.UNKNOW;没有执行到就宕机的情况
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
}


public class TransactionProducerDemo {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        //创建一个事务生产者TransactionMQProducer
        TransactionMQProducer producer = new TransactionMQProducer("transaction-msg-test");
        producer.setNamesrvAddr("192.168.18.142:9876");
        //创建一个线程池来执行事务检查
        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);

        //创建事务监听器
        TransactionListenerDemo transactionListenerDemo = new TransactionListenerDemo();
        producer.setTransactionListener(transactionListenerDemo);

        producer.start();
        String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
        //发送消息
        for (int i = 0; i < 10; i++) {
            try {
                Message msg =
                        new Message("TopicTestTransation21",
                                ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                Thread.sleep(10);
            } catch (MQClientException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < 100000; i++) {
            Thread.sleep(1000);
        }
    }
}

结果:
在这里插入图片描述
由逻辑可得0、4、8 %4=0 ,属于提交消息,1、5、9 %4=1 属于回滚消息。2、6%4=2 属于回查中提交消息。3、7%4=3,回查中回滚消息,与结果正确。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值