Spring Boot 整合RocketMq批量发送消息

1,120 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

背景

当系统发送的消息过多,每次发送消息都需要和MQ建立连接,对性能造成一定的影响,为了解决这个问题,RocketMq提供了批量发送消息机制,从而提升消息发送的性能。

概述

批量发送消息就是将消息进行打包批量发送,但是RocetMQ发送批量消息有如下的相关限制

  • 1.所有批量发送的消息的topic必须相同,且消息不能为延时消息
  • 2.一批批量发送消息的大小不能超过4M.

生产者消息大小计算

Producer发送消息Message的结构如下图:

图片.png

生产者的消息由四部分组成:Topic、消息Body、消息日志 (占20字节)及用于描述消息的一堆属性key-value;这些属性中包含例如生产者地址、生产时间、 要发送的QueueId等,生产者通过send()方法将消息生成一个字符串发送给Broker。

具体实现

生产者

 public void sendBatchMessage() throws UnsupportedEncodingException
   {
       List<Message> msgs = new ArrayList<>();{
       for (int i = 0; i < 5; i++) 
       {
           Message msg = new Message("test-batch-rocketmq",("Hello Message:" + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
           msgs.add(msg); 
       }
       rocketMQTemplate.convertAndSend("test-batch-rocketmq",msgs);
   }
  }

消费者

@Component
@RocketMQMessageListener(consumerGroup="test-batch-group",topic="test-batch-rocketmq",messageModel = MessageModel.CLUSTERING)
public class BatchConsumer implements RocketMQListener<String>
{
    private Logger logger =LoggerFactory.getLogger(getClass());
    @Override
    public void onMessage(String message)
    {
        logger.info("send succss content is:{}", message);
    }
}

测试结果

 c.s.fw.mq.consumer.BatchConsumer - send succss content is:
[{
	"topic": "test-batch-rocketmq",
	"flag": 0,
	"properties": {
		"WAIT": "true"
	},
	"body": "SGVsbG8gTWVzc2FnZTow",
	"transactionId": null,
	"keys": null,
	"tags": null,
	"delayTimeLevel": 0,
	"waitStoreMsgOK": true,
	"buyerId": null
}]

说明:从输出结果来上看,批量发送消息已经成功。如果发送消息的大小超过了4M,则会报如下错误:

CODE: 13  DESC: the message body size over max value, MAX: 4096

解决方案

当发送的消息内容超过了4M的时候,可以采用如下方案:

  • 修改MQ配置,调整生产者消费者发送消息的大小。
  • 将消息切割成功多条消息发送。

修改MQ配置,调整生产者消费者发送消息的大小。

需要设置生产者和消费者maxMessageSize的大小即可

  maxMessageSize: 5120

注意:maxMessageSize值设置的越大,Consumer每拉取一次需要的时间就会越长,且在网络上传输出现问题的可能性就越高;若在拉取过程中若出现了问题,那么本批次所有消息都需要全部重新拉取。对性能影响非常大,需要慎重考虑。

将消息切割成功多条消息发送。

消息切割工具类

public class MessageSplitUtil implements Iterator<List<Message>> 
{
    private final int MAX_SIZE = 4 * 1024 * 1024;
    
    private final List<Message> messages;
    
    // 
    private int currentIndex;
    
    public MessageSplitUtil(List<Message> messages) 
    {
        this.messages = messages;
    }
    @Override
    public boolean hasNext() 
    {
        return currentIndex < messages.size();
    }
    
    @Override
    public List<Message> next() 
    {
        int nextIndex = currentIndex;
        // 记录当前要发送的这一小批次消息列表的大小
        int totalSize = 0;
        for (; nextIndex < messages.size(); nextIndex++) 
        {
            // 获取当前遍历的消息
            Message message = messages.get(nextIndex);
            // 统计当前遍历的message的大小
            int tmpSize = message.getTopic().getBytes().length + message.getBody().length;
            Map<String, String> properties = message.getProperties();
            for (Map.Entry<String, String> entry : properties.entrySet()) 
            {
                tmpSize += entry.getKey().getBytes().length + entry.getValue().length();
            }
            tmpSize = tmpSize + 20;
            // 判断当前消息本身是否大于4M
            if (tmpSize > MAX_SIZE) {
                if (nextIndex - currentIndex == 0) 
                {
                    nextIndex++;
                }
                break;
            }
            // 当前消息的大小 + 之前统计要发送这一小批次消息列表的大小 》极限值4M
            if (tmpSize + totalSize > MAX_SIZE) 
            {
                break;
            }
            else 
            {
                // 统计要发送这一小批次消息列表的大小
                totalSize += tmpSize;
            }
        }
        List<Message> subList = messages.subList(currentIndex, nextIndex);
        currentIndex = nextIndex;
        return subList;
    }
}

说明:将消息进行切割成功多段返回。

生产者

   public void sendBatchMessage2() throws UnsupportedEncodingException
   {
       List<Message> msgs = new ArrayList<>();{
       for (int i = 0; i < 100; i++) 
       {
           Message msg = new Message("test-batch-rocketmq",("Hello Message:" + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
           msgs.add(msg); 
       }
       
       MessageSplitUtil messageSplitUtil =new MessageSplitUtil(msgs);
       
       while(messageSplitUtil.hasNext())
       {
         try
        {
           List<Message> messageItems=messageSplitUtil.next();
           rocketMQTemplate.convertAndSend("test-batch-rocketmq",messageItems);
        }
        catch (Exception e)
        {
            logger.error("send batch message error",e);
        }
       }
    }
  }

消费者

  @Component
@RocketMQMessageListener(consumerGroup="test-batch-group",topic="test-batch-rocketmq",messageModel = MessageModel.CLUSTERING)
public class BatchConsumer implements RocketMQListener<List<MessageExt>>
{
    private Logger logger =LoggerFactory.getLogger(getClass());

    @Override
    public void onMessage(List<MessageExt> message)
    {
        for(MessageExt messageExt:message)
        {
            logger.info("message content:{}",messageExt);
        }            
    }
}

总结

本文讲解了Spring Boot 整合RocketMq批量发送消息,结合实际的应用场景,批量发送消息能够提升消息发送的性能。