概述
RocketMQ的批量消息MessageBatch的消息编码格式如下:
【4 byte totalSize】【4 byte magicCod】【4 byte bodyCrc】【4 byte flag】【4 byte bodyLen】【bodyLen byte body】【2 byte propertiesLength】【propertiesLength byte propertiesBytes】
消息编码核心方法是: String#getBytes(String charsetName)方法
消息解码核心方法是:new String(byte bytes[], Charset charset)方法
对Message的属性集合的编码方式是:
- 将map集合转为String字符串。String字符串的形式为: key1value2key1value2key1value... 。使用"2"作为属性之间的分隔字符,使用"1"作为属性的key/value之间的分隔字符。
- String字符串转为字节数组。使用String#getBytes(String charsetName)方法实现。
批量消息编码
MessageBatch#encode()方法
private final List<Message> messages;
public byte[] encode() {
return MessageDecoder.encodeMessages(messages);
}
MessageDecoder#encodeMessages()方法
对消息集合的编码
public static byte[] encodeMessages(List<Message> messages) {
//TO DO refactor, accumulate in one buffer, avoid copies
List<byte[]> encodedMessages = new ArrayList<byte[]>(messages.size());
int allSize = 0;
for (Message message : messages) {
byte[] tmp = encodeMessage(message);
encodedMessages.add(tmp);
allSize += tmp.length;
}
byte[] allBytes = new byte[allSize];
int pos = 0;
for (byte[] bytes : encodedMessages) {
System.arraycopy(bytes, 0, allBytes, pos, bytes.length);
pos += bytes.length;
}
return allBytes;
}
MessageDecoder#encodeMessage()方法
对单条消息的编码
public static byte[] encodeMessage(Message message) {
//only need flag, body, properties
byte[] body = message.getBody();
int bodyLen = body.length;
String properties = messageProperties2String(message.getProperties());
byte[] propertiesBytes = properties.getBytes(CHARSET_UTF8);
//note properties length must not more than Short.MAX
short propertiesLength = (short) propertiesBytes.length;
int sysFlag = message.getFlag();
int storeSize = 4 // 1 TOTALSIZE
+ 4 // 2 MAGICCOD
+ 4 // 3 BODYCRC
+ 4 // 4 FLAG
+ 4 + bodyLen // 4 BODY
+ 2 + propertiesLength;
ByteBuffer byteBuffer = ByteBuffer.allocate(storeSize);
// 1 TOTALSIZE
byteBuffer.putInt(storeSize);
// 2 MAGICCODE
byteBuffer.putInt(0);
// 3 BODYCRC
byteBuffer.putInt(0);
// 4 FLAG
int flag = message.getFlag();
byteBuffer.putInt(flag);
// 5 BODY
byteBuffer.putInt(bodyLen);
byteBuffer.put(body);
// 6 properties
byteBuffer.putShort(propertiesLength);
byteBuffer.put(propertiesBytes);
return byteBuffer.array();
}
批量消息解码
MessageDecoder#decodeMessages()方法
对ByteBufer的解码,返回Message集合
public static List<Message> decodeMessages(ByteBuffer byteBuffer) throws Exception {
//TO DO add a callback for processing, avoid creating lists
List<Message> msgs = new ArrayList<Message>();
while (byteBuffer.hasRemaining()) {
Message msg = decodeMessage(byteBuffer);
msgs.add(msg);
}
return msgs;
}
MessageDecoder#decodeMessage()方法
对单条消息的解码
public static Message decodeMessage(ByteBuffer byteBuffer) throws Exception {
Message message = new Message();
// 1 TOTALSIZE
byteBuffer.getInt();
// 2 MAGICCODE
byteBuffer.getInt();
// 3 BODYCRC
byteBuffer.getInt();
// 4 FLAG
int flag = byteBuffer.getInt();
message.setFlag(flag);
// 5 BODY
int bodyLen = byteBuffer.getInt();
byte[] body = new byte[bodyLen];
byteBuffer.get(body);
message.setBody(body);
// 6 properties
short propertiesLen = byteBuffer.getShort();
byte[] propertiesBytes = new byte[propertiesLen];
byteBuffer.get(propertiesBytes);
message.setProperties(string2messageProperties(new String(propertiesBytes, CHARSET_UTF8)));
return message;
}
RocketMQ发送批量消息示例
String topic = "BatchTest";
List<Message> messages = new ArrayList<>();
//要求批量消息的每个Message的topic必须是相同的
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();
//handle the error
}
RocketMQ发送批量消息流程分析
DefaultMQProducer#send()方法
public SendResult send(
Collection<Message> msgs) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return this.defaultMQProducerImpl.send(batch(msgs));
}
DefaultMQProducer#batch()方法
private MessageBatch batch(Collection<Message> msgs) throws MQClientException {
MessageBatch msgBatch;
try {
//根据Collection<Message>集合构建MessageBatch
//构建过程中,前置检验每个Message的topic必须是相同的
//最后将Message集合作为MessageBatch的内部成员变量List<Message>
msgBatch = MessageBatch.generateFromList(msgs);
for (Message message : msgBatch) {
Validators.checkMessage(message, this);
MessageClientIDSetter.setUniqID(message);
message.setTopic(withNamespace(message.getTopic()));
}
//将MessageBatch的内部成员变量List<Message>进行编码,并将编码后的字节数组设置为内部成员变量byte[] body
msgBatch.setBody(msgBatch.encode());
} catch (Exception e) {
throw new MQClientException("Failed to initiate the MessageBatch", e);
}
msgBatch.setTopic(withNamespace(msgBatch.getTopic()));
return msgBatch;
}