RocketMQ消息发送样例【五】

一、基本样例

消息发送

1)发送同步消息、

这种可以性同步地发送方式使用的比较广泛,比如:重要消息通知,短信通知


public class SyncProducer {
    public static void main(String[] args) throws Exception {
//        1.创建消息生产者producer,并制定生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
//        2.指定Nameserver地址
        producer.setNamesrvAddr("192.168.0.123:9876;192.168.0.22:9876");
//        3.启动producer
        producer.start();
        for(int i=0; i< 10; i++){
            //创建消息并指定topic,tag消息体
            Message msg = new Message("TopicTest",
                    "TagA",
                    ("hello RocketMQ - " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            //5.发送消息
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达
            System.out.printf("%s%n", "发送消息状态" + sendResult);
        }
        // 如果不再发送消息,关闭Producer实例。
        producer.shutdown();
    }
}

2)发送异步消息

异步发送消息通常用在对响应时间铭感的业务场景, 即发送端不能容忍时间地等待Broker的响应。


public class AsyncProducer {

    public static void main(String[] args) throws Exception{
        //实例化消息生产者
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        //设置nameserver地址
        producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //启动producer实例
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);
        for (int i = 0; i < 2; i++) {
            final int index = i;
            //创建消息并指定Topic,Tag和消息体
            Message msg = new Message("TopicTest",
                    "TagA",
                    "OrderID188",
                    "Hello Word".getBytes(RemotingHelper.DEFAULT_CHARSET));
            //sendCallback接收异步返回结果回调
            producer.send(msg, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.printf("%-10d ok %s %n", index, sendResult.getMsgId());
                }

                @Override
                public void onException(Throwable throwable) {
                    System.out.printf("%-10d Exception %s %n", index, throwable, throwable.getStackTrace());
                }
            });
        }
        //不发送消息关闭producer 实例
//        producer.shutdown();
    }

3)单向发送消息

这种方式主要用在不特别关心发送结果的场景,例如日志发送


public class OnewayProducer {

    public static void main(String[] args) throws Exception{
        //实例化消息生产者Producer
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        //这只NameServer 的地址
        producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //启动Producer实例
        producer.start();
        for (int i=0; i<4; i++){
            //创建消息,并指定,Topic,Tag和消息体
            Message msg = new Message("TopicTest",
                    "TagA",
                    ("Hello Oneway RocketMQ - " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
            );
            //发送单向消息,没有任何返回结果
            producer.sendOneway(msg);
        }
        producer.shutdown();
    }
}

消费消息

1)负载均衡模式

消费者采用负载均衡方式消费消息,多个消息者共同消费队列消息,每个消费者处理的消息不同


public class Consumption {

    public static void main(String[] args) throws Exception{
        //实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        //指定nameserver地址
        consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //订阅topic
        consumer.subscribe("TopicTest", "*");
        //负载均衡消息
        consumer.setMessageModel(MessageModel.CLUSTERING);
        //注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                Iterator<MessageExt> it = msgs.iterator();
                while(it.hasNext()){
                    MessageExt me = it.next();
                    if(me.getTopic().equals("TopicTest")){
                        byte[] bytes = me.getBody();
                        System.out.printf("消息内容是:%s %n", new String(bytes));
                    }

                }
                System.out.printf("%s Receive New Message: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        //启动消费者
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

2)广播模式

消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的


public class Broadcast {

    public static void main(String[] args) throws  Exception{
        // 实例化消息生产者,指定组名
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        // 指定指定NameServer地址信息
        consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        // 订阅Topic
        consumer.subscribe("TopicTest", "*");
        // 负载均衡模式消费
        consumer.setMessageModel(MessageModel.BROADCASTING);
        // 注册回调函数,处理消息
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf("%s BroadCast Receive New Message: %s %n",
                        Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        // 启动消息
        consumer.start();
        System.out.printf("Consumer Started. %n");
    }
}

二、顺序消息

顺序消息生产

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

顺序消费的原理解析,在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。


public class Producer {
    public static void main(String[] args) throws Exception{

        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");

        producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");

        producer.start();

        String[] tags = new String[]{"TagA", "TagC", "TagD"};

        //订单列表
        List<OrderStep> orderStepList  = new Producer().buildOrders();

        Date date = new Date();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M|M-dd HH:mm:ss");
        String dateStr = sdf.format(date);

        for(int i=0; i<10; i++){
            //加时间戳
            String body = dateStr + " Hello RocketMQ " + orderStepList.get(i);
            Message message = new Message("TopicTest", tags[i % tags.length], "KEY" + i, body.getBytes());
            SendResult sendResult = producer.send(message, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message message, Object o) {
                    long id = (Long)o; //根据订单id选择发送queue
                    long index = id % mqs.size();
                    return mqs.get((int)index);
                }
            }, orderStepList.get(i).getOrderId()); //订单id
            System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
                    sendResult.getSendStatus(),
                    sendResult.getMessageQueue().getQueueId(),
                    body
            ));
        }
//        producer.shutdown();
    }

    /**
     * 生成模拟订单数据
     */
    private List<OrderStep> buildOrders() {
        List<OrderStep> orderList = new ArrayList<OrderStep>();

        OrderStep orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111065L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103117235L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(15103111039L);
        orderDemo.setDesc("完成");
        orderList.add(orderDemo);

        return orderList;
    }
}

public class OrderStep {
    private long orderId;
    private String desc;

    public long getOrderId() {
        return orderId;
    }

    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "OrderStep{" +
                "orderId=" + orderId +
                ", desc='" + desc + '\'' +
                '}';
    }
}

顺序消费消息

public class ConsumerInOrder {
    public static void main(String[] args) throws Exception{
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");
        consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        /**
         * 设置Consumer 第一次启动时从队列头部开始消费还是从队列尾部开始消费<br>
         *  如果非第一次启动那么按照上次消费的顺序继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicTest", "TagA || TagC || TagD");

        consumer.registerMessageListener(new MessageListenerOrderly() {
            Random random = new Random();
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for(MessageExt msg:msgs){
                    //可以看到每一个queue都有唯一的线程来消费,订单对应每一个queue(却分)有序
                    System.out.println("consumerThread=" + Thread.currentThread().getName() + "queue=" + msg.getQueueId() + ",content:" + new String(msg.getBody()));
                }
                try {
                    //模拟业务逻辑处理中。。。。
                    TimeUnit.SECONDS.sleep(random.nextInt(10));
                }catch(Exception e){
                    e.printStackTrace();
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.println("Consumer Started.");
    }
}

测试结果如下图所示


三、延时消息

延时消息消费者

public class ScheduledMessageConsumer{

    public static void main(String[] args) throws Exception {
        // 实例化消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer");
        consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //订阅Topics
        consumer.subscribe("TestTopic", "*");
        //注册消费则监听
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
                for(MessageExt message : messages){
                    //print approximate delay time period
                    System.out.println("Receive message [msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later");
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        //启动消费者
        consumer.start();
        System.out.println("Consumer Started.");
    }
}

延时消息生产者

public class ScheduledMessageProducer {
    public static void main(String[] args) throws Exception{
        //实例化一个生产者来生产延迟消息
        DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
        producer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //启动生产者
        producer.start();
        int totalMessageToSend = 3;
        for(int i=0; i < totalMessageToSend; i++){
            Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
            //设置延迟等级3,这个消息将在10秒之后发送(现在只支持固定的几个时间, 详情请看delayTimeLevel)
            message.setDelayTimeLevel(3);
            //发送消息
            producer.send(message);
        }
        producer.shutdown();
    }
}

使用限制

// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

现在RocketMq并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18

测试结果如下图所示:

四、批量消息

发送批量消息

如果每次只发送不超过4MB的消息,则很容易使用处理,样例如下:


String topic = "BatchTest";
List<Message> messages = new ArrayList<>();
messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
try {
   producer.send(messages);
} catch (Exception e) {
   e.printStackTrace();
   //处理error
}

如果消息的总长度可能大于4MB时,这时候最好把消息进行分割


public class ListSplitter implements Iterator<List<Message>> {
   private final int SIZE_LIMIT = 1024 * 1024 * 4;
   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; // 增加日志的开销20字节
           if (tmpSize > SIZE_LIMIT) {
               //单个消息超过了最大的限制
               //忽略,否则会阻塞分裂的进程
               if (nextIndex - currIndex == 0) {
                  //假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环
                  nextIndex++;
               }
               break;
           }
           if (tmpSize + totalSize > SIZE_LIMIT) {
               break;
           } else {
               totalSize += tmpSize;
           }

       }
       List<Message> subList = messages.subList(currIndex, nextIndex);
       currIndex = nextIndex;
       return subList;
   }
}
//把大的消息分裂成若干个小的消息
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
  try {
      List<Message>  listItem = splitter.next();
      producer.send(listItem);
  } catch (Exception e) {
      e.printStackTrace();
      //处理error
  }
}

五、过滤消息

在大多数情况下,TAG是一个简单而且有用的设计,其可以来选择你想要的消息。例如:


DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");

消息者将接收包含TAGA或TAGB或TAGC的消息,但是限制是一个消息只能有一个标签,者对于复杂场景可能不起作用。在这种情况下哎,可以使用SQL表达是筛选消息。SQL特性可以通过发送消息时的属性进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个例子:


------------
| message  |
|----------|  a > 5 AND b = 'abc'
| a = 10   |  --------------------> Gotten
| b = 'abc'|
| c = true |
------------
------------
| message  |
|----------|   a > 5 AND b = 'abc'
| a = 1    |  --------------------> Missed
| b = 'abc'|
| c = true |
------------

SQL基本语法


RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。


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

常量支持类型为:


  • 数值,比如:123,2.1315
  • 字符,比如:'abc',必须用单引号包裹起来;
  • null,特殊的常量
  • 布尔值,TRUEFALSE

只有使用push模式的消费者才能使用SQL92标准语句,接口入校:


public void subscribe(finalString topic, final MessageSelector messageSelector)

消息生产者


返送消息时,你能通过putUserProperty来设置消息的属性


public class Producer {

    public static void main(String[] args) throws Exception{
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
        producer.setNamesrvAddr("192.168.0.123:9876;192.168.0.22:9876");
        producer.start();
        String[] tags = {"TagA", "TagB", "TagC", "TagD"};
        for(int i=0; i< 20; i++){
            //创建消息并指定topic,tag消息体
            String tag = tags[i % tags.length];
            //构建消息
            Message msg = new Message("TopicTest",
                    tag,
                    ("hello RocketMQ age- " + i + ",消息TAG - " + tag ).getBytes(RemotingHelper.DEFAULT_CHARSET));
            msg.putUserProperty("age", String.valueOf(i));
            //5.发送消息
            SendResult sendResult = producer.send(msg);
            // 通过sendResult返回消息是否成功送达TopicTest
            System.out.printf("%s%n", "发送消息状态" + sendResult);
        }
        producer.shutdown();

    }
}

消息消费者


用MessageSelector.bySql来使用sql筛选消息


public class Consumer {

    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
        //指定nameserver地址
        consumer.setNamesrvAddr("192.168.0.22:9876;192.168.0.123:9876");
        //只有订阅的消息有这个属性a, a>=0 and a<3
        consumer.subscribe("TopicTest", MessageSelector.bySql("age between 5 and 13"));
        //负载均衡消息
        consumer.setMessageModel(MessageModel.CLUSTERING);
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                try {
                    for(MessageExt msg:msgs){
                        String msgbody  = new String(msg.getBody(), "utf-8");
                        SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
                        Date date = new Date(msg.getStoreTimestamp());
                        System.out.println("comsumer===  存入时间:" + sdf.format(date) + " === MessageBody:" +msgbody );
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.println("Consumer===启动成功!");
    }
}

测试结果如下图所示:

事务消息

流程分析

上面说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送以及提交、事务消息的补偿流程

1)是无消息发送及提交

(1)发送消息(half消息)
(2)服务端响应消息写入结果
(3)根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
(4)根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)

2)事务补偿

(1)对没有Commit/Rollback的是无消息(pending状态的消息),从服务端发起一次"回查"
(2)Producer收到回查消息,检查会差消息对应的本地事务的状态
(3)根据本地事务状态,重新Commit或者Rollback发生超时或者失败的情况

3)事务消息状态

事务消息共有三种状态,提交状态、回滚状态、中间状态:

  • TransactionStatus.CommitTransaction:提交事务,它允许消费者消费消息。
  • TransactionStatus.RollbackTransaction:回滚事务,它代表消息被删除,不允许被消费。
  • TransactionStatus.Unknown:中间状态,它代表需要检查消息队列来确定状态。

4)创建事务性生产者


使用TransactionMQProducer类创建生产者,并指定唯一的ProducerGroup,就可以设置自定义线程池来处理这些检查请求。执行本地事务后、需要根据执行结果对消息队列进行回复。回传的事务状态再请参考前一节。

public class Producer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        //创建事务监听器
        TransactionListener transactionListener = new TransactionListenerImpl();
        //创建消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("group6");
        producer.setNamesrvAddr("192.168.25.135:9876;192.168.25.138:9876");
        //生产者这是监听器
        producer.setTransactionListener(transactionListener);
        //启动消息生产者
        producer.start();
        String[] tags = new String[]{"TagA", "TagB", "TagC"};
        for (int i = 0; i < 3; i++) {
            try {
                Message msg = new Message("TransactionTopic", 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);
                TimeUnit.SECONDS.sleep(1);
            } catch (MQClientException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        //producer.shutdown();
    }
}

5)实现事务的监听接口


当发送半消息成功时,我们使用executeLocalTransaction方法来执行本地事务。它返回前一节中提到的三个事务状态之一。checkLocalTrancation方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。


public class TransactionListenerImpl implements TransactionListener {

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        System.out.println("执行本地事务");
        if (StringUtils.equals("TagA", msg.getTags())) {
            return LocalTransactionState.COMMIT_MESSAGE;
        } else if (StringUtils.equals("TagB", msg.getTags())) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        } else {
            return LocalTransactionState.UNKNOW;
        
	
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        System.out.println("MQ检查消息Tag【"+msg.getTags()+"】的本地事务执行结果");
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

使用限制

  1. 事务消息不支持延迟消息和批量消息
  2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为15次,但是用户可以通过Broker配置文件transactionCheckMax茶树来修改此限制。如果已经检查某条消息超过N次的话(N=TransactionCheckMax)则Broker将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写AbtractTransactionCheckListener类。来修改这个行为。
  3. 事务消息将在Broker配置文件中的参数tansactionMsgTimeout这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性CHECK_IMMUNITY_IN_SECOND 来改变这个限制,改参数优先于transactionMsgTimeout属性
  4. 事务性消息可能不止一次被检查或消费。
  5. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定,它的高可用性通过RocketMQ本身的高可用性机制来保证,如果希望确保消息不丢失、并且事务完整性得到保证,建议使用同步的双重写机制
  6. 事务消息的生产者ID不能与其它类型消息的生产者ID共享。与其它类型的消息不同,事务消息允许反向查询、MQ服务器通过他们的生产者ID查询消费者。
posted @ 2021-09-23 14:41  it鱼说  阅读(399)  评论(0)    收藏  举报