RocketMQ网络通信源码

一、概述

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 中的连接管理在高效、可靠的状态下运行。它通常用于 BrokerNameServer 中与客户端或其他组件的通信。

它的一个具体工作流程如下:

  • 连接建立以及激活之后,线程在运行的过程中,如果发生了跟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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值