1. 主题
RocketMQ作为一个分布式消息队列,通信的质量决定其成功。RocketMQ站在巨人的肩膀上,使用Netty库作为通信功能,决定了其高并发与稳定的通信,是一种聪明的做法。RocketMQ通信层处于remoting 包中,如下图:
2. 实现
2.1 重要的抽象类图:
- 顶层抽象类定义了 RemotingServer.java;
- 以及服务端 RemotingServer.java 与 客户端 RemotingClient.java;
通信抽象类
package org.apache.rocketmq.remoting;
public interface RemotingService {
void start();
void shutdown();
void registerRPCHook(RPCHook rpcHook);
}
- start方法;开启服务;
- shutdown方法;关闭服务;
- registerRPCHook; 注册 hook (可以在调用之前和调用之后做一些扩展处理)
2.2 自定义协议
首先关注protocol包,由包名“望文生义”,该包下是协议相关类。我们注意到
- 请求类型
RemotingCommandType, 分为请求与响应命令。
package org.apache.rocketmq.remoting.protocol;
public enum RemotingCommandType {
REQUEST_COMMAND,
RESPONSE_COMMAND;
}
- 序列化 SerializeType 定义了传输协议的序列化方式:
- JSON序列化; 对应RemotingSerializeable.java
- ROCKETMQ 自定义序列化;对应RocketMQSerializable.java
public enum SerializeType {
JSON((byte) 0),
ROCKETMQ((byte) 1);
private byte code;
- 协议相关类 RemotingCommand.java 相关属性,如下:
private int code;
private LanguageCode language = LanguageCode.JAVA;
private int version = 0;
private int opaque = requestId.getAndIncrement();
private int flag = 0;
private String remark;
private HashMap<String, String> extFields;
private transient CommandCustomHeader customHeader;
private SerializeType serializeTypeCurrentRPC = serializeTypeConfigInThisServer;
private transient byte[] body;
-
code: 请求命令编码,请求命令类型。
-
version: 版本号。
-
opaque: 客户端请求号。
-
flag: 标记。倒数第一位标识请求类型,0: 请求; 1: 返回。倒数第二位, 1: 表示oneway。
-
remark: 描述。
-
extFieIds: 扩展属性。
-
customeHeader: 每个请求对应的请求头信息.
-
byte[] body: 消息体内容。
3. 相关Netty Server 启动类
@Override
public void start() {
this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
nettyServerConfig.getServerWorkerThreads(),
new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
}
});
ServerBootstrap childHandler =
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_KEEPALIVE, false)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())
.childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())
.localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME,
new HandshakeHandler(TlsSystemConfig.tlsMode))
.addLast(defaultEventExecutorGroup,
new NettyEncoder(),
new NettyDecoder(),
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
new NettyConnectManageHandler(),
new NettyServerHandler()
);
}
});
从以上可以看到RocketMQ通信需要在字节码与和对象RemotingCommand之前频繁转换。 通过在pipeline()中添加编解码器实现,具体如下:
package org.apache.rocketmq.remoting.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.nio.ByteBuffer;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.common.RemotingUtil;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NettyDecoder extends LengthFieldBasedFrameDecoder {
private static final Logger log = LoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING);
private static final int FRAME_MAX_LENGTH =
Integer.parseInt(System.getProperty("com.rocketmq.remoting.frameMaxLength", "16777216"));
public NettyDecoder() {
super(FRAME_MAX_LENGTH, 0, 4, 0, 4);
}
@Override
public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = null;
try {
frame = (ByteBuf) super.decode(ctx, in);
if (null == frame) {
return null;
}
ByteBuffer byteBuffer = frame.nioBuffer();
return RemotingCommand.decode(byteBuffer);
} catch (Exception e) {
log.error("decode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e);
RemotingUtil.closeChannel(ctx.channel());
} finally {
if (null != frame) {
frame.release();
}
}
return null;
}
}
通过NettyDecoder解码得到去掉4个字节长度。结合,根据RemotingCommand#decode方法:
public static RemotingCommand decode(final ByteBuffer byteBuffer) {
int length = byteBuffer.limit();
int oriHeaderLen = byteBuffer.getInt();
int headerLength = getHeaderLength(oriHeaderLen);
byte[] headerData = new byte[headerLength];
byteBuffer.get(headerData);
RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen));
int bodyLength = length - 4 - headerLength;
byte[] bodyData = null;
if (bodyLength > 0) {
bodyData = new byte[bodyLength];
byteBuffer.get(bodyData);
}
cmd.body = bodyData;
return cmd;
}
4.Netty命令格式
我们知道ByteBuffer是Netty的数据容器,从以上代码能知道RemotingCommand的格式如下: