RocketMQ底层通信采用Netty,Netty为了解决粘包、半包问题提供了一些解码器:
- FixedLengthFrameDecoder:
定长解码器,根据固定长度接收消息,容易浪费带宽且解决不了半包问题 - DelimiterBasedFrameDecoder
特定分隔符解码器,如名,每次都要遍历到分隔符,效率较低 - LenghtFieldBasedFrameDecode
消息头长度解码器,消息中会包含一些长度信息,根据这些长度信息解码,不错的选择
RocketMQ解码采用的是LenghtFieldBasedFrameDecode
public class NettyDecoder extends LengthFieldBasedFrameDecoder {
// 省略
}
编码
org.apache.rocketmq.remoting.netty.NettyEncoder#encode
@ChannelHandler.Sharable
public class NettyEncoder extends MessageToByteEncoder<RemotingCommand> {
private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME);
// 4个字节填充物 + 4字节总长度 + 1字节协议类型 + 3字节消息头长度 + 消息头 + 消息体
@Override
public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out)
throws Exception {
try {
remotingCommand.fastEncodeHeader(out);
byte[] body = remotingCommand.getBody();
if (body != null) {
out.writeBytes(body);
}
} catch (Exception e) {
log.error("encode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e);
if (remotingCommand != null) {
log.error(remotingCommand.toString());
}
RemotingHelper.closeChannel(ctx.channel());
}
}
}
public void fastEncodeHeader(ByteBuf out) {
int bodySize = this.body != null ? this.body.length : 0;
// 拿到起始写位置
int beginIndex = out.writerIndex();
// skip 8 bytes
// 为了凑齐2的整数幂(32位)
out.writeLong(0);
int headerSize;
if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) {
if (customHeader != null && !(customHeader instanceof FastCodesHeader)) {
this.makeCustomHeaderToNet();
}
headerSize = RocketMQSerializable.rocketMQProtocolEncode(this, out);
} else {
this.makeCustomHeaderToNet();
byte[] header = RemotingSerializable.encode(this);
headerSize = header.length;
out.writeBytes(header);
}
// 写入整个消息的长度,4为消息长度本身占用的长度
out.setInt(beginIndex, 4 + headerSize + bodySize);
// 跳过上一步设置的消息长度所占的4个字节,设置协议类型以及消息头长度(第一个字节为序列化类型,后面三个是消息头长度)
out.setInt(beginIndex + 4, markProtocolType(headerSize, serializeTypeCurrentRPC));
}
解码
org.apache.rocketmq.remoting.protocol.RemotingCommand#decode(io.netty.buffer.ByteBuf)
public static RemotingCommand decode(final ByteBuf byteBuffer) throws RemotingCommandException {
// 获取可读字节数
int length = byteBuffer.readableBytes();
// header长度4个字节32位
int oriHeaderLen = byteBuffer.readInt();
// 取oriHeaderLen低24位,前8位没有实际意义,只是为了凑2的整数幂(32)
int headerLength = getHeaderLength(oriHeaderLen);
if (headerLength > length - 4) {
//todo-tzx 22480 2024/6/19 22:39 什么情况会走到这里?
throw new RemotingCommandException("decode error, bad header length: " + headerLength);
}
// 解码header
RemotingCommand cmd = headerDecode(byteBuffer, headerLength, getProtocolType(oriHeaderLen));
// 解码body
int bodyLength = length - 4 - headerLength;
byte[] bodyData = null;
if (bodyLength > 0) {
bodyData = new byte[bodyLength];
byteBuffer.readBytes(bodyData);
}
cmd.body = bodyData;
return cmd;
}
根据编解码代码可以得知,RocketMQ协议为:
4个字节填充物 + 4字节总长度 + 1字节协议类型 + 3字节消息头长度 + 消息头 + 消息体