RocketMQ 中Netty源码分析

1,202 阅读2分钟

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包,由包名“望文生义”,该包下是协议相关类。我们注意到

  1. 请求类型

RemotingCommandType, 分为请求与响应命令。

package org.apache.rocketmq.remoting.protocol;

public enum RemotingCommandType {
    REQUEST_COMMAND,
    RESPONSE_COMMAND;
}
  1. 序列化 SerializeType 定义了传输协议的序列化方式:
  • JSON序列化; 对应RemotingSerializeable.java
  • ROCKETMQ 自定义序列化;对应RocketMQSerializable.java
public enum SerializeType {
    JSON((byte) 0),
    ROCKETMQ((byte) 1);

    private byte code;
  1. 协议相关类 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);
       }![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b5bad45871b44eaada2b93b28589c70~tplv-k3u1fbpfcp-watermark.image)
       cmd.body = bodyData;

       return cmd;
   }

4.Netty命令格式

我们知道ByteBuffer是Netty的数据容器,从以上代码能知道RemotingCommand的格式如下: