AnthonyZero's Bolg

Netty线程模型

线程模型

Netty主要基于主从Reactors多线程模型(如下图)做了一定的修改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:MainReactor负责客户端的连接请求,并将请求转交给SubReactor。SubReactor负责相应通道的IO读写请求非IO请求(具体逻辑处理)的任务则会直接写入队列,等待worker threads进行处理

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

netty2.jpg

说明:虽然Netty的线程模型基于主从Reactor多线程,借用了MainReactor和SubReactor的结构,但是从代码里看来,它并没有Thread Pool这个东西。Netty的subReactor与worker thread是同一个线程,采用IO多路复用机制。

  • bossGroup线程池则只是在bind某个端口后,获得其中一个线程作为MainReactor,专门处理端口的accept事件,每个端口对应一个boss线程。
  • workerGroup线程池会被各个SubReactor和worker线程充分利用
疑问:为什么不管线程池多大,Boss线程池只会使用一个线程,既然只使用一个线程为什么要用线程池呢?

解答:boss 线程池是处理 accept事件的,如果是异常的情况下,线程die了,可以再创建一个新线程。当ServerBootstrap bind多个端口时,boss线程池可以使用多个线程来accept事件。

NioEventLoop

NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:

  1. I/O任务 即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。
  2. 非IO任务 添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。
  • 对Thread的封装
    NioEventLoopGroup维护的是事件循环,EventLoop, 在Netty的主从Reactor线程模型中,两个事件循环组其实也是线程组,因为每一个EventLoop在他的整个生命周期中都始终和一条线程唯一绑定,EventLoop的线程使用的是它自己封装的FastThreadLocalThread, 这条线程使得EventLoop有了处理事件的能力

  • 对Selector的封装
    NioEventLoopGroup维护的是事件循环,EventLoop,同样维护着属于自己的Selector选择器,这个选择器使得EventLoop拥有了轮询绑定在自己身上的Channel的能力. 并且Netty对JDK原生的选择器做出了升级,使用自定义的数组替换了原生Selector的HashSet集合SelectedKeys,使得时间的复杂度在任何时刻都是O1

工作原理

netty1.jpg

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 I/O事件;
  2. 处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理
  3. 处理任务队列中的任务,runAllTasks。