netty学习之Reactor线程模型以及在netty中的应用

1.Reactor单线程模型

Reactor单线程模型就是指全部的IO操做都在同一个NIO线程上面完成的,也就是IO处理线程是单线程的。NIO线程的职责是: 
(1)做为NIO服务端,接收客户端的TCP链接;react

(2)做为NIO客户端,向服务端发起TCP链接;数据库

(3)读取通讯对端的请求或则应答消息;后端

(4)向通讯对端发送消息请求或则应答消息。安全

Reactor单线程模型图以下所示: 
单线程Reactor网络

Reactor模式使用的是同步非阻塞IO(NIO),全部的IO操做都不会致使阻塞,理论上一个线程能够独立的处理全部的IO操做(selector会主动去轮询哪些IO操做就绪)。从架构层次看,一个NIO线程确实能够完成其承担的职责,好比上图的Acceptor类接收客户端的TCP请求消息,当链路创建成功以后,经过Dispatch将对应的ByteBuffer转发到指定的handler上,进行消息的处理。多线程

对于一些小容量的应用场景下,可使用单线程模型,可是对于高负载、大并发的应用场景却不适合,主要缘由以下: 
(1)一个NIO线程处理成千上万的链路,性能没法支撑,即便CPU的负荷达到100%;架构

(2)当NIO线程负载太重,处理性能就会变慢,致使大量客户端链接超时而后重发请求,致使更多堆积未处理的请求,成为性能瓶颈。并发

(3)可靠性低,只有一个NIO线程,万一线程假死或则进入死循环,就彻底不可用了,这是不能接受的。异步

基于上诉问题,提出了Reactor的多线程模型:oop

2.Reactor多线程模型

Reactor多线程模型与单线程模型最大的区别在于,IO处理线程再也不是一个线程,而是一组NIO处理线程。原理以下图所示: 
这里写图片描述

Reactor多线程模型的特色以下: 
(1)有一个专门的NIO线程—-Acceptor线程用于监听服务端,接收客户端的TCP链接请求。

(2)网络IO操做—-读写等操做由一个专门的线程池负责,线程池可使用JDK标准的线程池实现,包含一个任务队列和N个可用的线程,这些NIO线程就负责读取、解码、编码、发送。

(3)一个NIO线程能够同时处理N个链路,可是一个链路只对应一个NIO线程。

Reactor多线程模型能够知足绝大多数的场景,除了一些个别的特殊场景:好比一个NIO线程负责处理客户全部的链接请求,可是若是链接请求中包含认证的需求(安全认证),在百万级别的场景下,就存在性能问题了,由于认证自己就要消耗CPU,为了解决这种情景下的性能问题,产生了第三种线程模型:Reactor主从线程模型。

3.主从Reactor多线程模型

主从Reactor线程模型的特色是:服务端用于接收客户端链接的再也不是一个单独的NIO线程,而是一个独立的NIO的线程池。Acceptor接收到客户端TCP链接请求并处理完成后(可能包含接入认证),再将新建立的SocketChannel注册到IO线程池(sub reactor)的某个IO处理线程上并处理编解码和读写工做。Acceptor线程池仅负责客户端的链接与认证,一旦链路链接成功,就将链路注册到后端的sub Reactor的IO线程池中。 线程模型图以下: 
这里写图片描述

利用主从Reactor模型能够解决服务端监听线程没法有效处理全部客户链接的性能不足问题,这也是netty推荐使用的线程模型。

4. netty的线程模型

netty的线程模型是能够经过设置启动类的参数来配置的,设置不一样的启动参数,netty支持Reactor单线程模型、多线程模型和主从Reactor多线程模型。 
这里写图片描述

服务端启动时建立了两个NioEventLoopGroup,一个是boss,一个是worker。实际上他们是两个独立的Reactor线程池,一个用于接收客户端的TCP链接,另外一个用于处理Io相关的读写操做,或则执行系统的Task,定时Task。

Boss线程池职责以下: 
(1)接收客户端的链接,初始化Channel参数 
(2)将链路状态变动时间通知给ChannelPipeline

worker线程池做用是: 
(1)异步读取通讯对端的数据报,发送读事件到ChannelPipeline 
(2)异步发送消息到通讯对端,调用ChannelPipeline的消息发送接口 
(3)执行系统调用Task; 
(4)执行定时任务Task;

经过配置boss和worker线程池的线程个数以及是否共享线程池等方式,netty的线程模型能够在单线程、多线程、主从线程之间切换。

为了提高性能,netty在不少地方都进行了无锁设计。好比在IO线程内部进行串行操做,避免多线程竞争形成的性能问题。表面上彷佛串行化设计彷佛CPU利用率不高,可是经过调整NIO线程池的线程参数,能够同时启动多个串行化的线程并行运行,这种局部无锁串行线程设计性能更优。 

nettyd的NioEventLoop读取到消息以后,直接调用ChannelPipeline的fireChannelRead(Object msg),只要用户不主动切换线程,一直都是由NioEventLoop调用用用户的Handler,期间不进行线程切换,这种串行化设计避免了多线程操做致使的锁竞争,性能角度看是最优的。

5. netty的线程模型 设置最佳实践

(1)建立两个NioEventLoopGroup,隔离NIO Acceptor和NIO的IO线程。

(2)尽可能不要在ChannelHandler中启动用户线程(解码以后,将POJO消息派发到后端的业务线程池除外)。

(3)解码要放在NIO线程调用的Handler中,不要放在用户线程中解码。

(4)若是IO操做很是简单,不涉及复杂的业务逻辑计算,没有可能致使阻塞的磁盘操做、数据库操做、网络操做等,能够再NIO线程调用的Handler中完成业务逻辑,不要切换到用户线程。  (5)若是IO业务操做比较复杂,就不要在NIO线程上完成,由于阻塞可能会致使NIO线程假死,严重下降性能。这时候能够把POJO封装成Task,派发到业务线程池中由业务线程处理,以保证NIO,线程被尽快的释放,处理其他的IO操做。

相关文章
相关标签/搜索