一、概述
RocketMQ为了保证消息在发送过程中满足高吞吐、高可靠、低延迟,它的底层是基于Netty实现的,本小节主要简单介绍一下Netty的基础概念以及RocketMQ底层的网络通信是怎么实现的。
二、Netty基础概念
我们先来看一下他的逻辑架构图:
- 网络通信层
网络通信层的职责是执行网络 I/O 的操作。它支持多种网络协议和 I/O 模型的连接操作。当网络数据读取到内核缓冲区后,会触发各种网络事件,这些网络事件会分发给事件调度层进行处理。
网络通信层的核心组件包含BootStrap、ServerBootStrap、Channel三个组件。
Bootstrap 负责 Netty 客户端程序的启动、初始化、服务器连接等过程,串联了 Netty 的其他核心组件。
ServerBootStrap 用于服务端启动绑定本地端口,会绑定Boss 和 Worker两个 EventLoopGroup。
Channel 的是“通道”,Netty Channel提供了基于NIO更高层次的抽象,如 register、bind、connect、read、write、flush 等。
- 事件调度层
事件调度层的职责是通过 Reactor 线程模型对各类事件进行聚合处理,通过 Selector 主循环线程集成多种事件( I/O 事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层中相关的 Handler 完成。
事件调度层的核心组件包括 EventLoopGroup、EventLoop。
EventLoopGroup 是Netty 的核心处理引擎,本质是一个线程池,主要负责接收 I/O 请求,并分配线程执行处理请求。
- 服务编排层
服务编排层的职责是负责组装各类服务,它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。
服务编排层的核心组件包括 ChannelPipeline、ChannelHandler、ChannelHandlerContext。
ChannelPipeline 是 Netty 的核心编排组件,负责组装各种 ChannelHandler,ChannelPipeline 内部通过双向链表将不同的 ChannelHandler 链接在一起。当 I/O 读写事件触发时,Pipeline 会依次调用 Handler 列表对 Channel 的数据进行拦截和处理。
ChannelHandler 完成数据的编解码以及处理工作。
ChannelHandlerContext 用于保存Handler 上下文,通过 HandlerContext 我们可以知道 Pipeline 和 Handler 的关联关系。
在了解完Netty的基础概念之后,我们接下来看看他在RocketMQ里面的使用
三、RocketMQ网络通信解析
我们在前面文章提到过,NameServer在启动过程中,他首先加载我们的配置,然后启动网络通信组件,我们看一下他们的一个启动过程,它的本质其实就是启动Netty:
// 远程网络通信服务器,跟broker、producer、consumer来进行网络通信的
// 他内部必然是基于netty启动一个网络服务器,同时你可以注册一些请求处理组件,你还可以通过他
// 对client/broker发起一个rpc调用,同步、异步、oneway
private RemotingServer remotingServer;
public void start() throws Exception {
this.remotingServer.start();
if (this.fileWatchService != null) {
this.fileWatchService.start();
}
}
通过上面我们对Netty有了一个简单的了解,Netty在启动的时候是构建出一个ServerBootstrap,构建的具体如下:
// 真正的在这里创建netty server
ServerBootstrap childHandler = this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector) //设置两个EventLoopGroup
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) //设置通道类型
.option(ChannelOption.SO_BACKLOG, nettyServerConfig.getServerSocketBacklog()) // tcp三次握手的accept队列长度
.option(ChannelOption.SO_REUSEADDR, true) // server socket channel unbind端口监听了以后,还处于延迟unbind状态,重新启动允许我们可以立马监听这个端口
.option(ChannelOption.SO_KEEPALIVE, false) // 是否自动发送探测包探测网络连接是否还存活
.childOption(ChannelOption.TCP_NODELAY, true) // nodelay,禁止打包传输,避免通信有延迟
.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, handshakeHandler)
.addLast(defaultEventExecutorGroup,
encoder,
new NettyDecoder(),
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
connectionManageHandler,
serverHandler
);
}
});
这段代码的目的是为每个新的 SocketChannel
创建并配置一条处理管道(pipeline)。其中包含多个处理器,用于处理网络连接的不同任务,比如握手、数据编码/解码、空闲检测、连接管理和具体的业务逻辑处理。每个处理器按照顺序依次处理传入的事件或数据。
它的一个具体流程如下:
- 首先执行握手过程(
handshakeHandler
)。 - 然后处理数据的编码和解码(
encoder
和NettyDecoder
)。 - 接着监控连接是否空闲(
IdleStateHandler
)。 - 处理连接的管理(
connectionManageHandler
)。 - 最后进行实际的业务处理(
serverHandler
)。
HandshakeHandler 握手组件
当物理连接建立了以后,人家可以传输请求数据过来,请求读取出来字节数据,一定要先经过我的握手handler。
它的一个具体流程如下:
- 先根据消息的第一个字节来判断一下,这个消息是不是一个握手消息
- 重置索引,以便握手协商可以正常进行
-
从handler链条里把当前的这个handler就可以移除掉了,一个连接建立了之后,这个handshake handler仅仅用一次就可以了,他就是针对第一条消息的
NettyConnectManageHandler 网络连接管理组件
NettyConnectManageHandler
主要用于管理 Netty Channel(即连接通道)的生命周期,处理连接的建立、关闭、异常捕获等,确保 RocketMQ 中的连接管理在高效、可靠的状态下运行。它通常用于 Broker 和 NameServer 中与客户端或其他组件的通信。
它的一个具体工作流程如下:
-
连接建立以及激活之后,线程在运行的过程中,如果发生了跟broker网络连接有事件,put netty event放到LinkedBlockingQueue这个队列里面去
-
线程池在执行过程中会从对列里面获取事件信息,根据事件信息的状态码去执行不同的操作
NettyServerHandler Netty消息处理组件
通常用于服务器端处理入站的消息。当 RocketMQ 作为服务器端运行时,它会使用NettyServerHandler
来接收并处理来自客户端(例如生产者或消费者)的请求消息。
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
processMessageReceived(ctx, msg);
}
}
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
final RemotingCommand cmd = msg;
if (cmd != null) {
switch (cmd.getType()) {
// 请求
case REQUEST_COMMAND:
processRequestCommand(ctx, cmd);
break;
// 响应
case RESPONSE_COMMAND:
processResponseCommand(ctx, cmd);
break;
default:
break;
}
}
}
我们通过上面的两组代码可以清楚的看到NettyServerHandler组件主要是用来处理我的请求/响应的。如果说broker主动对我的nameserver发起一个rpc调用请求,rpc调用和响应都是RemotingCommand,我的netty server会反序列化rpc调用请求,从字节数组搞成RemotingCommand,就会把这个rpc请求交给这个方法,来进行rpc请求处理,也有可能是nameserver发送了一个rpc请求给broker,broker返回的是rpc响应,我收到的也可能是一个响应。
我们首先来看一下他是如何处理发送过来的请求的:
- 根据请求过来的request code在请求处理表processorTable中找到该映射中的响应处理器来处理请求
/*
* 这个容器包含每个请求的所有处理器代码,也就是说,对于每个传入的请求,我们可以查找
* 该映射中的响应处理器来处理请求。
*/
protected final HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>> processorTable =
new HashMap<Integer, Pair<NettyRequestProcessor, ExecutorService>>(64);
-
收到了请求之后,对于不同的请求处理组件可以关联不同的请求处理线程池,如果说请求处理组件是异步化的,可以对这个请求命令可以做一个异步化的处理,同时传递进去netty请求处理上下文,响应callback,否则请求处理会委托给具体的请求处理组件,不同的请求会由不同的请求处理组件来进行处理,处理完了以后会拿到响应,会走一个callback
-
会把线程任务、发送请求过来的网络连接、请求命令封装成RequestTask(本质上是一个线程,RequestTask实现了Runnable接口)
-
找到请求处理组件关联的一个线程池,把这个请求任务提交到线程池里去
接着我们来看一下他的响应处理过程(场景:broker给NameServer发送请求,NameServer给Broker一个响应):
- broker给NameServer发送请求,他会拿到一个请求id opaque
-
封装一个ResponseFuture,这个就代表了后续我要获取到的一个响应future对象,此时响应command是空的,因为他是同步调用的,拿到了响应之后,就直接返回响应对象就可以了,异步发送请求,他会传递进来一个invokeCallback,通过异步回调机制来接收我的响应
final ResponseFuture responseFuture = new ResponseFuture(
channel, opaque, timeoutMillis, null, null);
-
把请求id->空的responseFuture放入到map里去
responseTable.put(opaque, responseFuture);
- 接着到我们处理响应的一个过程,首先他会根据请求id,获取ResponseFuture
-
如果是oneway的话,其实是没有future,把请求发送出去就可以了
-
如果是sync/async是有future的,此时就要考虑同步还是异步的方式,如果是同步的方式则采用countDown进行控制,反之就交给我们的线程池去发送
四、小结
我们从Netty的基础介绍,从而引入到RocketMQ网络通信解析,主要了解RocketMQ底层数据通过同步/异步,oneway如何发送和接收响应的,从而更好的去了解RocketMQ。