2024-01-19
原文作者: 墨家巨子@俏如来 原文地址: https://blog.csdn.net/u014494148/article/details/120661361

使用场景

如果消息过多,每次发送消息都和MQ建立连接,无疑是一种性能开销,批量消息可以把消息打包批量发送,批量发送消息能显著提高传递小消息的性能。

批量消息概述

批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB,如果超过可以有2种处理方案:

1.将消息进行切割成多个小于4M的内容进行发送

2.修改4M的限制改成更大

  • 可以设置Producer的maxMessageSize属性
  • 修改配置文件中的maxMessageSize属性

对于消费者而言Consumer的MessageListenerConcurrently监听接口的consumeMessage()方法的第一个参数为消息列 表,但默认情况下每次只能消费一条消息,可以通过:Consumer的pullBatchSize属性设置消息拉取数量(默认32),可以通过设置consumeMessageBatchMaxSize属性设置消息一次消费数量(默认1)。

[注意]:pullBatchSize 和 consumeMessageBatchMaxSize并不是设置越大越好,一次拉取数据量太大会导致长时间等待,性能降低。而且消息处理失败同一批消息都会失败,然后进行重试,导致消费时长增加。增加没必要的重试次数。

批量消息实战

生产者

我们需要做什么

  • 定义消息切割器切割消息
  • 发送消息把消息切割之后,进行多次批量发送
定义消息切割器
    //消息切割器,按照4M大小写个
    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 startIndex = getStartIndex();
            int nextIndex = startIndex;
            int totalSize = 0;
            for (; nextIndex < messages.size(); nextIndex++) {
                Message message = messages.get(nextIndex); 
                int tmpSize = calcMessageSize(message);
                if (tmpSize + totalSize > SIZE_LIMIT) {
                    break; 
                } else {
                    totalSize += tmpSize; 
                }
            }
            List<Message> subList = messages.subList(startIndex, nextIndex); 
            currIndex = nextIndex;
            return subList;
        }
        private int getStartIndex() {
            Message currMessage = messages.get(currIndex); 
            int tmpSize = calcMessageSize(currMessage); 
            while(tmpSize > SIZE_LIMIT) {
                currIndex += 1;
                Message message = messages.get(currIndex);
                tmpSize = calcMessageSize(message);
            }
            return currIndex; 
        }
        private int calcMessageSize(Message message) {
            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字节
            return tmpSize; 
        }
    }
消息发送
    public class BatchProducer {
    
        //演示消息同步发送
        public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException {
            //生产者
            DefaultMQProducer producer = new DefaultMQProducer("batch-producerGroup");
    
            //设置name server地址
            producer.setNamesrvAddr("127.0.0.1:9876");
            //设置最大消息大小,默认4M
            producer.setMaxMessageSize(1024 * 1024 * 4);
            //启动
            producer.start();
    
    
            //===========准备消息==========================================================
            List<Message> messages = new ArrayList<>();
    
            for (long i = 0 ; i < 10000 ; i++){
                //添加内容
                byte[] bytes = ("批量消息".getBytes(CharsetUtil.UTF_8));
                Message message = new Message("topic-order-batch","product-order-batch",bytes);
                message.setKeys("key-"+i);
                messages.add(message);
            }
            //===========切割消息==========================================================
    
            //把大的消息分裂成若干个小的消息
            ListSplitter splitter = new ListSplitter(messages);
    
            while (splitter.hasNext()) {
                try {
                	//安装4m切割消息
                    List<Message>  listItem = splitter.next();
                    //发送消息
                    SendResult sendResult = producer.send(listItem);
                    System.out.println(sendResult);
                } catch (Exception e) {
                    e.printStackTrace();
                    //处理error
                }
            }
    
            producer.shutdown();
        }
    }

消费者

我们要做什么

  • 可以指定消息拉取数量和消费数量
    public class BatchConsumer {
        public static void main(String[] args) throws MQClientException {
            //创建消费者
            DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer("batch-consumerGroup");
            //设置name server 地址
            defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876");
            //从开始位置消费
            defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
            //批量拉取消息数量,默认32
            defaultMQPushConsumer.setPullBatchSize(32);
            //每次消费条数,默认1
            defaultMQPushConsumer.setConsumeMessageBatchMaxSize(10);
    
            //订阅
            defaultMQPushConsumer.subscribe("topic-order-batch","product-order-batch");
    
            defaultMQPushConsumer.registerMessageListener(new MessageListenerConcurrently() {
                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
    
                    list.forEach(message->{
                        System.out.println(message+" ; "+new String(message.getBody(), CharsetUtil.UTF_8));
                    });
    
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
    
            defaultMQPushConsumer.start();
        }
    }

文章结束希望对你有所帮助,看官高兴的话给个好评吧。

阅读全文