Netty源码03-Netty整体框架

Netty整体框架

前面两篇文章对Java NIO进行了详细的介绍和分析,也给下面分析Netty源码打下一定的基础

netty框架参考博文:
https://www.cnblogs.com/imstudy/p/9908791.html
https://blog.csdn.net/u013857458/article/details/82527722

Java已经有了一个原生的NIO框架,为什么还会出现Netty呢,这个原因主要有两个:
  • Java的NIO还不够高效,其底层使用selector,而Netty使用Linux下最高效的I/O模式epoll
  • Selector多路复用的开发模式较为复杂,需要在程序中自己轮询,而且SelectionKey需要自己进行删除的管理,比较容易出错,而且由很多阻塞操作(select),Java自带的AIO更加难用。Netty是全异步操作,并且将底层IO操作全部封装,简化开发
  • Java的NIO内存管理采用ByteBufferByteBuffer是出了名的难用,在使用的时候要是忘记flip()很容易出错。Netty提供的ByteBuf就好用了很多,其采用读写双Index,更加易用

Netty的线程模型

Netty 主要基于主从 Reactors 多线程模型,但是做了一定的修改,其中主从 Reactor 多线程模型有主从两个Reactor:

  • MainReactor 负责客户端的连接请求,并将请求转交给 SubReactor;
  • SubReactor 负责相应通道的 IO 读写请求;
  • 3)非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。

这里引用 Doug Lee 大神的 Reactor 介绍——Scalable IO in Java 里面关于主从 Reactor 多线程模型的图:
19f012f3a45df5432ab2bb4c2e5251a4.jpg
需要注意的是:虽然 Netty 的线程模型基于主从 Reactor 多线程,借用了 MainReactor 和 SubReactor 的结构。但是实际实现上 SubReactor 和 Worker 线程在同一个线程池中

    EventLoopGroup bossGroup = new NioEventLoopGroup(10);
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup,workerGroup);
    b.channel(NioServerSocketChannel.class);

上面代码中的 bossGroup 和 workerGroup 是 Bootstrap 构造方法中传入的两个对象,这两个 group 均是EventLoopGroup,也就是事件循环组,这里暂且把它认为是线程池

  • NioEventLoopGroup: NIO的事件循环组,如果不指定初始化线程数量,将默认初始化CPU内核数 * 2个线程
    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }
    /**
     * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
     */
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }
  • bossGroup: 事件循环组只是在 Bind 某个端口后,获得其中一个线程作为 MainReactor,专门处理端口的 Accept 事件,每个端口对应一个 Boss 线程
  • workerGroup: 事件循环组负责处理真正的逻辑会被各个 SubReactor 和 Worker 线程充分利用

Netty Reactor工作框架图

Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。
NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。

每个 Boss NioEventLoop 循环执行的任务包含 3 步:
1)轮询 Accept 事件;
2)处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;
3)处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。

每个 Worker NioEventLoop 循环执行的任务包含 3 步:
1)轮询 Read、Write 事件;
2)处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
3)处理任务队列中的任务,runAllTasks。
image.png

Netty的异步处理模式

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者
Netty 中的 I/O 操作都是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture
调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果
当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作
常见有如下操作:

  • 通过 isDone 方法来判断当前操作是否完成
  • 通过 isSuccess 方法来判断已完成的当前操作是否成功
  • 通过 getCause 方法来获取已完成的当前操作失败的原因
  • 通过 isCancelled 方法来判断已完成的当前操作是否被取消
  • 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器
例如下面的代码中绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑:
serverBootstrap.bind(port).addListener(future -> {

       if(future.isSuccess()) {

           System.out.println(newDate() + ": 端口["+ port + "]绑定成功!");

       } else{

           System.err.println("端口["+ port + "]绑定失败!");

       }

   });
我们也可用sync方法把异步操作变为同步操作,具体代码如下:
    try {
         ChannelFuture f = b.bind(12345).sync();
         if(f.isSuccess()){
            System.out.println("服务器启动成功");
         }
         f.channel().closeFuture().sync();
     } catch (InterruptedException e) {
         e.printStackTrace();
     }

Netty 框架的架构

Netty 功能特性

  • 传输服务:支持 BIO 和 NIO
  • 容器集成:支持 OSGI、JBossMC、Spring、Guice 容器
  • 协议支持:HTTP、Protobuf、二进制、文本、WebSocket 等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议
  • Core 核心:可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象

Netty 模块

Netty 模块组件

image.png

1. Bootstrap、ServerBootstrap

Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。
ServerBootstrap.png
Bootstrap.png

2. Future、ChannelFuture

正如前面介绍,在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。
但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 FutureChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

3. Channel

Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel是被注册到EventLoop上的。Channel 为用户提供:

  • 当前网络连接的通道的状态(例如是否打开?是否已连接?)
  • 网络连接的配置参数 (例如接收缓冲区大小)
  • 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。
  • 调用立即返回一个 ChannelFuture 实例,通过注册监听器(addListener)到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。
  • 支持关联 I/O 操作与对应的处理程序。

不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应
下面是一些常用的 Channel 类型:

  • NioSocketChannel,异步的客户端 TCP Socket 连接。
  • NioServerSocketChannel,异步的服务器端 TCP Socket 连接,统一使用水平触发
  • EpollServerSocketChannel,异步的服务器端 TCP Socket 连接,统一使用边缘触发以获得最大性能,这个只能在Linux机器上使用
/**
 * {@link ServerSocketChannel} implementation that uses linux EPOLL Edge-Triggered Mode for
 * maximal performance.
 */
  • NioDatagramChannel,异步的 UDP 连接。
  • NioSctpChannel,异步的客户端 Sctp 连接。
  • NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
4. NioEventLoop事件执行器

NioEventLoop 就是异步IO处理网络连接的生命周期中发生的各种事件,其中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:

  • I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。
  • 非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。

两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。

5. NioEventLoopGroup事件循环组

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个事件执行器的组(线程池),内部维护了一组循环线程,每个线程负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。每个线程执行都调用next函数把任务交给下一个线程执行,这样就可以获得Future,保证了Netty的全异步执行
image.png

6. Channel、EventLoop(Group)和ChannelFuture
  • Channel:代表一个Socket链接
  • ChannelFuture:异步通知,ServerBootstrap bind端口返回ChannelFuture
  • EventLoop:循环处理网络连接的生命周期中发生的各种事件

image.png

EventLoop就是单线程的事件循环执行器,EventLoop组合成EventLoopGroupChannel被创建后就注册在了一个EventLoop上Channel在整个生命周期内使用EventLoop处理IO事件

image.png
image.png

关系说明:
  • 一个EventLoopGroup 包含一个或者多个EventLoop
  • 一个EventLoop 在它的生命周期内只和一个Thread 绑定
  • 所有由EventLoop 处理的I/O 事件都将在它专有的Thread 上被处理
  • 一个Channel 在它的生命周期内只注册于一个EventLoop
  • 一个EventLoop 可能会被分配给一个或多个Channel
7. ChannelHandler、ChannelPipeline和ChannelHandlerContext
  • ChannelHandler:应用程序开发人员的角度来看,Netty 的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的地方。Netty 以适配器类的形式(这里的适配器并不是设计模式中的适配器模式,更像是模版方法)提供了大量默认的ChannelHandler 实现,帮我们简化应用程序处理逻辑的开发过程
    ChannelHandler的适配器
    image.png
  • ChannelPipeline:提供了ChannelHandler 链的容器(责任链模式),并定义了用于在该链上传播入站和出站事件流的API。当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了ChannelHandler 和ChannelPipeline 之间的绑定
    image.png
    image.png

    Netty会把出站Handler和入站Handler放到一个Pipeline中,物理视图上看是一个,从逻辑视图上看是两个。那么站在逻辑视图的角度,分属出站和入站不同的Handler ,是无所谓顺序的。而同属一个方向的Handler则是有顺序的,因为上一个Handler处理的结果往往是下一个Handler的要求的输入。将图 中的处理器(ChannelHandler)从左到右进行编号,那么入站事件按顺序看到的ChannelHandler 将是1,2,4,而出站事件按顺序看到的ChannelHandler 将是5,3
  • ChannelHandlerContext:创建ChannelHandler并绑定到ChannelPipline的时候Netty自动为该ChannelHandler生成相应ChannelHandlerContext,他代表了ChannelHandler和ChannelPipeline之间的绑定,在ChannelHandlerContext中可以拿到相应的Channel和ChannelPipeline

image.png

ChannelHandlerContext,Channel,Pipeline都有flush方法,区别:

  1. Channel,pipeline调用时将遍历所有ChannelHandler然后出站
  2. ChannelHandlerContext调用flush时将只调用他后面的ChannelHandler,所以一般都调用ChannelHandlerContext的WriteAndFlush函数
阅读 500

推荐阅读
苏格拉没有底
用户专栏

尽心,知命

4 人关注
30 篇文章
专栏主页