使用场景
如果消息过多,每次发送消息都和MQ建立连接,无疑是一种性能开销,批量消息可以把消息打包批量发送,批量发送消息能显著提高传递小消息的性能。
批量消息概述
批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB,如果超过可以有2种处理方案:
1.将消息进行切割成多个小于4M的内容进行发送
2.修改4M的限制改成更大
- 可以设置Producer的maxMessageSize属性
- 修改配置文件中的maxMessageSize属性
对于消费者而言Consumer的MessageListenerConcurrently监听接口的consumeMessage()方法的第一个参数为消息列 表,但默认情况下每次只能消费一条消息,可以通过:Consumer的pullBatchSize属性设置消息拉取数量(默认32),可以通过设置consumeMessageBatchMaxSize属性设置消息一次消费数量(默认1)。
[注意]:pullBatchSize 和 consumeMessageBatchMaxSize并不是设置越大越好,一次拉取数据量太大会导致长时间等待,性能降低。而且消息处理失败同一批消息都会失败,然后进行重试,导致消费时长增加。增加没必要的重试次数。
批量消息实战
生产者
我们需要做什么
- 定义消息切割器切割消息
- 发送消息把消息切割之后,进行多次批量发送
定义消息切割器
//消息切割器,按照4M大小写个publicclassListSplitterimplementsIterator<List<Message>> { privatefinalintSIZE_LIMIT=1024*1024*4; privatefinalList<Message>messages; privateintcurrIndex; publicListSplitter(List<Message>messages) { this.messages=messages; } publicbooleanhasNext() { returncurrIndex<messages.size(); } publicList<Message>next() { intstartIndex=getStartIndex(); intnextIndex=startIndex; inttotalSize=0; for (; nextIndex<messages.size(); nextIndex++) { Messagemessage=messages.get(nextIndex); inttmpSize=calcMessageSize(message); if (tmpSize+totalSize>SIZE_LIMIT) { break; } else { totalSize+=tmpSize; } } List<Message>subList=messages.subList(startIndex, nextIndex); currIndex=nextIndex; returnsubList; } privateintgetStartIndex() { MessagecurrMessage=messages.get(currIndex); inttmpSize=calcMessageSize(currMessage); while(tmpSize>SIZE_LIMIT) { currIndex+=1; Messagemessage=messages.get(currIndex); tmpSize=calcMessageSize(message); } returncurrIndex; } privateintcalcMessageSize(Messagemessage) { inttmpSize=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字节returntmpSize; } }
消息发送
publicclassBatchProducer { //演示消息同步发送publicstaticvoidmain(String[] args) throwsInterruptedException, RemotingException, MQClientException, MQBrokerException { //生产者DefaultMQProducerproducer=newDefaultMQProducer("batch-producerGroup"); //设置name server地址producer.setNamesrvAddr("127.0.0.1:9876"); //设置最大消息大小,默认4Mproducer.setMaxMessageSize(1024*1024*4); //启动producer.start(); //===========准备消息==========================================================List<Message>messages=newArrayList<>(); for (longi=0 ; i<10000 ; i++){ //添加内容byte[] bytes= ("批量消息".getBytes(CharsetUtil.UTF_8)); Messagemessage=newMessage("topic-order-batch","product-order-batch",bytes); message.setKeys("key-"+i); messages.add(message); } //===========切割消息==========================================================//把大的消息分裂成若干个小的消息ListSplittersplitter=newListSplitter(messages); while (splitter.hasNext()) { try { //安装4m切割消息List<Message>listItem=splitter.next(); //发送消息SendResultsendResult=producer.send(listItem); System.out.println(sendResult); } catch (Exceptione) { e.printStackTrace(); //处理error } } producer.shutdown(); } }
消费者
我们要做什么
- 可以指定消息拉取数量和消费数量
publicclassBatchConsumer { publicstaticvoidmain(String[] args) throwsMQClientException { //创建消费者DefaultMQPushConsumerdefaultMQPushConsumer=newDefaultMQPushConsumer("batch-consumerGroup"); //设置name server 地址defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876"); //从开始位置消费defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); //批量拉取消息数量,默认32defaultMQPushConsumer.setPullBatchSize(32); //每次消费条数,默认1defaultMQPushConsumer.setConsumeMessageBatchMaxSize(10); //订阅defaultMQPushConsumer.subscribe("topic-order-batch","product-order-batch"); defaultMQPushConsumer.registerMessageListener(newMessageListenerConcurrently() { publicConsumeConcurrentlyStatusconsumeMessage(List<MessageExt>list, ConsumeConcurrentlyContextconsumeConcurrentlyContext) { list.forEach(message->{ System.out.println(message+" ; "+newString(message.getBody(), CharsetUtil.UTF_8)); }); returnConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); defaultMQPushConsumer.start(); } }