使用场景
如果消息过多,每次发送消息都和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(); } }