• RocketMQ简单使用(二)批处理、过滤、事务消息


    简单研究下rockermq 批量消息、过滤消息、事务消息的使用。

    1. 批量消息

    1. 简介

       批量消息的发送能提升投递小消息的性能。但是批量消息有一些限制,一批投递的消息应该有相同的主题、具有相同的刷盘策略、不支持延时消息与事务型消息。

      另外,生产者发送消息的大小有一些限制。默认不超过1MB 的消息。如果超出可以将批量消息进行拆分。或者通过修改配置。

      生产者发送的消息结构如下:

      生产者发送的消息并不是将消息直接序列化后发送到网络上的,而是通过Message 对象生成了一个字符串发送出去的。这个字符串包含:Topic、消息body、消息日志(20字节)、以及用于描述消息属性的key-value 结构,这些属性包括生产者地址、生产时间、以及要发送的queueId 等。最终写入到broker 中消息单元中的数据都来自于这些属性。

    1. 批量消费消息

      org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently#consumeMessage 方法的第一个参数为消息列表,但是默认情况下每次只能消费一条。其规则简单理解是:一次性拉取 pullBatchSize(默认32) 参数指定的消息数量,然后按照consumeMessageBatchMaxSize (默认为1)指定的大小分发到多个消费线程。

    1. 代码

    (1)生产者代码

    package com.zd.bx.rocketmq.batch;
    
    import org.apache.rocketmq.client.producer.DefaultMQProducer;
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.common.message.Message;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    
    public class Producer2 {
    
        public static void main(String[] args) throws Exception {
            DefaultMQProducer producer = new DefaultMQProducer("batchProducer");
            producer.setNamesrvAddr("192.168.13.111:9876");
            producer.start();
    
            // 构造消息
            String topic = "batchTest";
            List<Message> messages = new ArrayList<>();
            for (int index = 0; index < 100000; index++) {
                messages.add(new Message(topic, "TagA", "OrderI" + index, ("Hello world " + index).getBytes()));
            }
    
    //        redirectSend(producer, messages);
    
            // 批量发送
            ListSplitter splitter = new ListSplitter(messages);
            int sendNum = 0;
            while (splitter.hasNext()) {
                System.out.println("sendNum: " + (++sendNum));
                try {
                    List<Message> listItem = splitter.next();
                    producer.send(listItem);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            producer.shutdown();
        }
    
        private static void redirectSend(DefaultMQProducer producer, List<Message> messages) {
            try {
                // 直接发送会报错: org.apache.rocketmq.client.exception.MQClientException: CODE: 13  DESC: the message body size over max value, MAX: 4194304
                SendResult send = producer.send(messages);
                System.out.println(send);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    class ListSplitter implements Iterator<List<Message>> {
    
        private final int SIZE_LIMIT = 1 * 1024 * 1024;
    
        private final List<Message> messages;
    
        private int currIndex;
    
        public ListSplitter(List<Message> messages) {
            this.messages = messages;
        }
    
        @Override
        public boolean hasNext() {
            return currIndex < messages.size();
        }
    
        @Override
        public List<Message> next() {
            int nextIndex = currIndex;
            int totalSize = 0;
            for (; nextIndex < messages.size(); nextIndex++) {
                Message message = messages.get(nextIndex);
                int tmpSize = message.getTopic().length() + message.getBody().length;
                Map<String, String> properties = message.getProperties();
                for (Map.Entry<String, String> entry : properties.entrySet()) {
                    tmpSize += entry.getKey().length() + entry.getValue().length();
                }
                tmpSize = tmpSize + 20; //for log overhead
                if (tmpSize > SIZE_LIMIT) {
                    //it is unexpected that single message exceeds the SIZE_LIMIT
                    //here just let it go, otherwise it will block the splitting process
                    if (nextIndex - currIndex == 0) {
                        //if the next sublist has no element, add this one and then break, otherwise just break
                        nextIndex++;
                    }
                    break;
                }
                if (tmpSize + totalSize > SIZE_LIMIT) {
                    break;
                } else {
                    totalSize += tmpSize;
                }
            }
            List<Message> subList = messages.subList(currIndex, nextIndex);
            currIndex = nextIndex;
            return subList;
        }
    }
    

    (2)批量消费者

    package com.zd.bx.rocketmq.batch;
    
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.List;
    
    public class PushConsumer {
    
        public static void main(String[] args) throws InterruptedException, MQClientException {
            // 定义一个push消费者
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myTestConsumerGroup");
            // 指定nameserver
            consumer.setNamesrvAddr("192.168.13.111:9876");
            // 指定消费的topic与tag
            consumer.subscribe("batchTest", "*");
            // 指定使用 广播模式进行消费,默认为集群模式
    //        consumer.setMessageModel(MessageModel.BROADCASTING);
            // 修改一次拉取的最大值(默认32),可以理解为一次拉取回来之后然后按下面的参数consumeMessageBatchMaxSize,分发到多个线程组中进行消费
            consumer.setPullBatchSize(60);
            // 指定每次可以消费100条消息(可以理解是下面监听器中集合的大小),默认为1,值范围是[1, 1024]
            consumer.setConsumeMessageBatchMaxSize(3);
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    System.out.println(Thread.currentThread().getName() + " msgs.size(): " + msgs.size());
                    for (MessageExt msg : msgs) {
                        System.out.printf(Thread.currentThread().getName() + " %s Receive New Messages: %s body: %s %n", Thread.currentThread().getName(), msgs, new String(msg.getBody()));
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            consumer.start();
            System.out.printf("Consumer Started.%n");
        }
    }
    

    2. 过滤消息

      在接收消息的时候除了可以指定消息的Topic外,还可以对指定Topic的消息进行过滤。可以根据Tag 进行过滤,也可以根据sql 过滤。sql过滤是对消息中属性进行筛选过滤,只有push模式的消费者才能使用sql过滤。

      sql过滤表达式中支持多种常量类型与运算符。支持的常量类型:

    • 数值:比如1,2,3
    • 字符:必须用单引号包裹,比如'abc'
    • 布尔:TRUE或者FALSE

       支持的运算符有:

    • 数值比较:>,<,>=,<=,BETWEEN,=
    • 字符比较:=,<>,IN
    • 逻辑比较:AND,OR,NOT
    • NULL判断: IS NULL 或者 IS NOT NULL

      默认情况下Broker没有开启sql过滤功能,需要在Broker加载的配置文件中添加如下属性:(单机在broker.conf 文件中)

    enablePropertyFilter=true
    

      测试代码:

    (1)生产者:自定义属性

    package com.zd.bx.rocketmq.filter;
    
    
    import org.apache.rocketmq.client.exception.MQBrokerException;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.client.producer.DefaultMQProducer;
    import org.apache.rocketmq.common.message.Message;
    import org.apache.rocketmq.remoting.exception.RemotingException;
    
    public class Producer {
    
        public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
            DefaultMQProducer producer = new DefaultMQProducer("batchProducer");
            producer.setNamesrvAddr("192.168.13.111:9876");
            producer.start();
    
            String[] tags = new String[]{"TagA", "TagB", "TagC"};
            String[] sexs = new String[]{"男", "女"};
            // 构造消息
            String topic = "filterTopic";
            for (int index = 0; index < 20; index++) {
                byte[] body = ("Hi, " + index).getBytes();
                String tag = tags[index % tags.length];
                Message message = new Message(topic, tag, body);
                // 存一个自定义属性age, 一个自定义属性sex
                message.putUserProperty("age", index + "");
                message.putUserProperty("sex", sexs[index % sexs.length]);
                System.out.println(producer.send(message));
            }
    
            producer.shutdown();
        }
    }
    

    (2)消费者

    消费者一:按标签进行过滤

    package com.zd.bx.rocketmq.filter;
    
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.List;
    
    public class Consumer1 {
    
        public static void main(String[] args) throws MQClientException {
            // 定义一个push消费者
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myTestConsumerGroup");
            // 指定从第一条消息开始消费
            // 指定nameserver
            consumer.setNamesrvAddr("192.168.13.111:9876");
            // 指定消费的topic与tag. "TagA || TagB" 等价于 MessageSelector.byTag("TagA || TagB")
            consumer.subscribe("filterTopic", "TagA || TagB");
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    for (MessageExt msg : msgs) {
                        System.out.printf("%s Receive Messages: %s, property: %s %n", Thread.currentThread().getName(), new String(msg.getBody()), msg.getProperties());
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            consumer.start();
            System.out.printf("Consumer Started.%n");
        }
    }
    

    消费者二:按sql条件进行过滤(接收age大于并且sex为男的)

    package com.zd.bx.rocketmq.filter;
    
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.MessageSelector;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.List;
    
    public class Consumer2 {
    
        public static void main(String[] args) throws MQClientException {
            // 定义一个push消费者
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myTestConsumerGroup");
            // 指定nameserver
            consumer.setNamesrvAddr("192.168.13.111:9876");
            // 指定消费的topic与tag
            consumer.subscribe("filterTopic", MessageSelector.bySql("age > 5 and sex = '男'"));
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    for (MessageExt msg : msgs) {
                        System.out.printf("%s Receive New Messages: %s, property: %s %n", Thread.currentThread().getName(),  new String(msg.getBody()), msg.getProperties());
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            consumer.start();
            System.out.printf("Consumer Started.%n");
        }
    }
    

    3. 事务消息

      事务型消息可以确保执行本地事务和发送消息保证原子性。这里需要注意是执行本地事务和发送消息是在一个事务里面。

    1. 举一个场景:工行用户A向建行用户B转账一万元。同步消息处理该需求的场景:

      这里有个问题:若第三步骤的扣款失败,消息已经到达Broker。对于MQ来说,只要消息写入MQ就可以被消费。此时建行系统增加了1万元。此时就出现了数据不一致的问题。

    1. 解决思路:

      使用MQ的事务消息。让1、2、3具有原子性,这里就是用到事务消息。但是对于4、5操作,是不在该事务中的,也就是事务消息只能保证本地事务和发送消息在是一致的。解决示意图如下:

    解释:第3步发送的消息是半事务消息(预提交到Broker),消费者是不能消费该消息的;第6步发送扣款执行结果,实际是向TC汇报本地事务的执行状态(LocalTransactionState里面的状态)。

    1. 事务消息的使用限制:

    (1)不支持定时和批处理消息

    (2)事务消息要做好幂等性检查,因为事务消息可能不止一次被消费(存在回滚后再提交的情况)

    1. 基本概念

    (1) 分布式事务:可以理解为多个本地事务的集合体,多个事务直接构成一个大的分布式事务。要么全失败,要么全成功。

    (2)事务消息:RocketMQ提供了类似于X/OpenXA的分布式事务功能,通过事务消息能达到分布式事务的最终一致性。XA是一种分布式事务解决方案,一种分布式事务处理模式。

    (3)半事务消息:暂不能投递的消息, 发送方已经成功地将消息发送到了Broker,但是Broker未收到最终确认指令,该消息被标记成"暂不能投递"状态,即不能为消费者看到。出于该状态下的消息成为半事务消息。

    (4)本地事务状态:Producer回调执行的结果为本地事务状态,其会发给TC(事务控制器),而TC再发给TM(事务管理器)。TM根据TC送来的本地事务状态来决定全局事务确认指令。

    package org.apache.rocketmq.client.producer;
    
    public enum LocalTransactionState {
        COMMIT_MESSAGE,    // 提交事务,表示允许消费者消费该消息
        ROLLBACK_MESSAGE,    // 消息回滚,类似于预提交的消息撤回
        UNKNOW,    // 未知,执行回调
    }
    

    (5)消息回查:重新查询本地事务的执行状态。关于消息回查的设置:

    transactionTimeout=20, 指定TM应该在20s内将消息发送给TC,否则引发消息回查。默认60s。

    transactionCheckMax=5,指定最多回查次数,超过后将丢弃消息并且记录日志。默认15次。

    transactionCheckInterval=10,指定设置的多次的消息回查的时间间隔为10s。默认为60s。

    1. 测试代码

    (1)监听器

    package com.zd.bx.rocketmq.tx;
    
    import org.apache.commons.lang3.StringUtils;
    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;
    
    public class Listener implements TransactionListener {
    
        // 回调操作方法
        // 消息预提交成功就会触发该方法的执行,用于完成本地事务
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            System.out.println("预提交消息成功:" + msg);
            // 假设接收到TagA的消息就表示扣款操作成功,TagB的消息表示扣款失败,
            // TagC表示扣款结果不清楚,需要执行消息回查
            if (StringUtils.equals("TagA", msg.getTags())) {
                return LocalTransactionState.COMMIT_MESSAGE;
            } else if (StringUtils.equals("TagB", msg.getTags())) {
                return LocalTransactionState.ROLLBACK_MESSAGE;
            } else if (StringUtils.equals("TagC", msg.getTags())) {
                return LocalTransactionState.UNKNOW;
            }
            return LocalTransactionState.UNKNOW;
        }
    
        // 消息回查方法
        // 引发消息回查的原因最常见的有两个:
        // 1)回调操作返回UNKNWON
        // 2)TC没有接收到TM的最终全局事务确认指令
        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            System.out.println("执行消息回查: body" + new String(msg.getBody()) + " tag: " + msg.getTags() + " txId: " + msg.getTransactionId());
            return LocalTransactionState.COMMIT_MESSAGE;
        }
    }
    

    (2)生产者

    package com.zd.bx.rocketmq.tx;
    
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.client.producer.SendResult;
    import org.apache.rocketmq.client.producer.TransactionMQProducer;
    import org.apache.rocketmq.common.message.Message;
    import org.apache.rocketmq.remoting.common.RemotingHelper;
    
    import java.io.UnsupportedEncodingException;
    import java.util.concurrent.*;
    
    public class Producer {
    
        public static void main(String[] args) throws InterruptedException, MQClientException {
            TransactionMQProducer producer = new TransactionMQProducer("transactionProducerGroup");
            producer.setNamesrvAddr("192.168.13.111: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);
            producer.setTransactionListener(new Listener());
            producer.start();
    
            String[] tags = new String[]{"TagA", "TagB", "TagC"};
            for (int i = 0; i < 10; i++) {
                try {
                    Message msg = new Message("txTopic", tags[i % tags.length], "KEY" + i,
                            ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                    SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                    System.out.printf("%s%n", sendResult);
    
                    Thread.sleep(10);
                } catch (MQClientException | UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
    
            for (int i = 0; i < 100000; i++) {
                Thread.sleep(1000);
            }
            producer.shutdown();
        }
    }
    

    日志如下:

    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY0, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9CF80000, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagA}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 48], transactionId='7F0000015BC818B4AAC2793C9CF80000'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9CF80000, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=2], queueOffset=40]
    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY1, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9D170001, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagB}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 49], transactionId='7F0000015BC818B4AAC2793C9D170001'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9D170001, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=3], queueOffset=41]
    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY2, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9D280002, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagC}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 50], transactionId='7F0000015BC818B4AAC2793C9D280002'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9D280002, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=0], queueOffset=42]
    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY3, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9D3D0003, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagA}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 51], transactionId='7F0000015BC818B4AAC2793C9D3D0003'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9D3D0003, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=1], queueOffset=43]
    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY4, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9D510004, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagB}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 52], transactionId='7F0000015BC818B4AAC2793C9D510004'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9D510004, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=2], queueOffset=44]
    预提交消息成功:Message{topic='txTopic', flag=0, properties={KEYS=KEY5, TRAN_MSG=true, UNIQ_KEY=7F0000015BC818B4AAC2793C9D650005, WAIT=true, PGROUP=transactionProducerGroup, TAGS=TagC}, body=[72, 101, 108, 108, 111, 32, 82, 111, 99, 107, 101, 116, 77, 81, 32, 53], transactionId='7F0000015BC818B4AAC2793C9D650005'}
    SendResult [sendStatus=SEND_OK, msgId=7F0000015BC818B4AAC2793C9D650005, offsetMsgId=null, messageQueue=MessageQueue [topic=txTopic, brokerName=broker-a, queueId=3], queueOffset=45]
    执行消息回查: bodyHello RocketMQ 2 tag: TagC txId: 7F0000015BC818B4AAC2793C9D280002
    执行消息回查: bodyHello RocketMQ 5 tag: TagC txId: 7F0000015BC818B4AAC2793C9D650005
    

    (3)消费者

    package com.zd.bx.rocketmq.tx;
    
    import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
    import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
    import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
    import org.apache.rocketmq.client.exception.MQClientException;
    import org.apache.rocketmq.common.message.MessageExt;
    
    import java.util.List;
    
    public class Consumer {
    
        public static void main(String[] args) throws MQClientException {
            // 定义一个push消费者
            DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("myTestConsumerGroup");
            // 指定nameserver
            consumer.setNamesrvAddr("192.168.13.111:9876");
            consumer.subscribe("txTopic", "*");
            consumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    for (MessageExt msg : msgs) {
                        System.out.printf("%s Receive Messages: %s, property: %s %n", Thread.currentThread().getName(), new String(msg.getBody()), msg.getProperties());
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            consumer.start();
            System.out.printf("Consumer Started.%n");
        }
    }
    

    日志如下:

    ConsumeMessageThread_myTestConsumerGroup_5 Receive Messages: Hello RocketMQ 0, property: {MIN_OFFSET=0, REAL_TOPIC=txTopic, MAX_OFFSET=4, KEYS=KEY0, TRAN_MSG=true, CONSUME_START_TIME=1648098015500, UNIQ_KEY=7F0000015BC818B4AAC2793C9CF80000, CLUSTER=DefaultCluster, PGROUP=transactionProducerGroup, WAIT=true, TAGS=TagA, REAL_QID=2} 
    ConsumeMessageThread_myTestConsumerGroup_6 Receive Messages: Hello RocketMQ 3, property: {MIN_OFFSET=0, REAL_TOPIC=txTopic, MAX_OFFSET=3, KEYS=KEY3, TRAN_MSG=true, CONSUME_START_TIME=1648098015559, UNIQ_KEY=7F0000015BC818B4AAC2793C9D3D0003, CLUSTER=DefaultCluster, PGROUP=transactionProducerGroup, WAIT=true, TAGS=TagA, REAL_QID=1} 
    ConsumeMessageThread_myTestConsumerGroup_7 Receive Messages: Hello RocketMQ 5, property: {TRANSACTION_CHECK_TIMES=1, TRAN_MSG=true, CONSUME_START_TIME=1648098030724, MIN_OFFSET=0, REAL_TOPIC=txTopic, MAX_OFFSET=4, KEYS=KEY5, UNIQ_KEY=7F0000015BC818B4AAC2793C9D650005, CLUSTER=DefaultCluster, PGROUP=transactionProducerGroup, WAIT=true, TAGS=TagC, REAL_QID=3} 
    ConsumeMessageThread_myTestConsumerGroup_8 Receive Messages: Hello RocketMQ 2, property: {TRANSACTION_CHECK_TIMES=1, TRAN_MSG=true, CONSUME_START_TIME=1648098030733, MIN_OFFSET=0, REAL_TOPIC=txTopic, MAX_OFFSET=4, KEYS=KEY2, UNIQ_KEY=7F0000015BC818B4AAC2793C9D280002, CLUSTER=DefaultCluster, PGROUP=transactionProducerGroup, WAIT=true, TAGS=TagC, REAL_QID=0} 
    
  • 相关阅读:
    阿里云服务器搭建之绑定多个域名
    centos7下yum安装mysql
    Mac 升级之后 无法通过域名 SSH 连接
    【课程章节更新】突破 没有支付权限的小程序 完成支付闭环
    每次sudo su切换root用户,都要source /etc/profile
    那些让你震惊的网站
    bootstrap select2使用模态框,搜索框无法输入问题解决
    新浪图片停止外链后的一些解决办法
    Yii2 报错 Headers already sent in
    使用Python Flask 开发微信机器人
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/16029672.html
Copyright © 2020-2023  润新知