Netty学习三:线程模型

1 Proactor和Reactor

Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发、高吞吐量的环境中进行I/O处理。

I/O多路复用机制都依赖于一个事件分发器,事件分离器把接收到的客户事件分发到不一样的事件处理器中,以下图:java

1.1 select,poll,epoll

在操做系统级别select,poll,epoll是3个经常使用的I/O多路复用机制,简单了解一下将有助于咱们理解Proactor和Reactor。

1.1.1 select

select的原理以下:react

用户程序发起读操做后,将阻塞查询读数据是否可用,直到内核准备好数据后,用户程序才会真正的读取数据。promise

poll与select的原理类似,用户程序都要阻塞查询事件是否就绪,但poll没有最大文件描述符的限制。

1.1.2 epoll

epoll是select和poll的改进,原理图以下:

epoll使用“事件”的方式通知用户程序数据就绪,而且使用内存拷贝的方式使用户程序直接读取内核准备好的数据,不用再读取数据缓存

1.2 Proactor

Proactor是一个异步I/O的多路复用模型,原理图以下:

  • 用户发起IO操做到事件分离器
  • 事件分离器通知操做系统进行IO操做
  • 操做系统将数据存放到数据缓存区
  • 操做系统通知分发器IO完成
  • 分离器将事件分发至相应的事件处理器
  • 事件处理器直接读取数据缓存区内的数据进行处理

1.3 Reactor

Reactor是一个同步的I/O多路复用模型,它没有Proactor模式那么复杂,原理图以下:

  • 用户发起IO操做到事件分离器
  • 事件分离器调用相应的处理器处理事件
  • 事件处理完成,事件分离器得到控制权,继续相应处理

1.4 Proactor和Reactor的比较

  • Reactor模型简单,Proactor复杂
  • Reactor是同步处理方式,Proactor是异步处理方式
  • Proactor的IO事件依赖操做系统,操做系统须支持异步IO
  • 同步与异步是相对于服务端与IO事件来讲的,Proactor经过操做系统异步来完成IO操做,当IO完成后通知事件分离器,而Reactor须要本身完成IO操做

2 Reactor多线程模型

前面已经简单介绍了Proactor和Reactor模型,在实际中Proactor因为须要操做系统的支持,实现的案例很少,有兴趣的能够看一下Boost Asio的实现,咱们主要说一下Reactor模型,Netty也是使用Reactor实现的。

但单线程的Reactor模型每个用户事件都在一个线程中执行:安全

  • 性能有极限,不能处理成百上千的事件
  • 当负荷达到必定程度时,性能将会降低
  • 单某一个事件处理器发送故障,不能继续处理其余事件

2.1 多线程Reactor

使用线程池的技术来处理I/O操做,原理图以下:

  • Acceptor专门用来监听接收客户端的请求
  • I/O读写操做由线程池进行负责
  • 每一个线程能够同时处理几个链路请求,但一个链路请求只能在一个线程中进行处理

2.2 主从多线程Reactor

在多线程Reactor中只有一个Acceptor,若是出现登陆、认证等耗性能的操做,这时就会有单点性能问题,所以产生了主从Reactor多线程模型,原理以下:

  • Acceptor再也不是一个单独的NIO线程,而是一个独立的NIO线程池
  • Acceptor处理完后,将事件注册到IO线程池的某个线程上
  • IO线程继续完成后续的IO操做
  • Acceptor仅仅完成登陆、握手和安全认证等操做,IO操做和业务处理依然在后面的从线程中完成

3 Netty中Reactor模型的实现

Netty同时支持Reactor的单线程、多线程和主从多线程模型,在不一样的应用中经过启动参数的配置来启动不一样的线程模型。

经过线程池的线程个数、是否共享线程池方式来切换不一样的模型网络

3.1 Netty中的Reactor模型

Netty中的Reactor模型以下图:多线程

  • Acceptor中的NioEventLoop用于接收TCP链接,初始化参数
  • I/O线程池中的NioEventLoop异步读取通讯对端的数据报,发送读事件到channel
  • 异步发送消息到对端,调用channel的消息发送接口
  • 执行系统调用Task
  • 执行定时Task

3.2 NioEventLoop

NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责以下:并发

1. 做为服务端Acceptor线程,负责处理客户端的请求接入
2. 做为客户端Connecor线程,负责注册监听链接操做位,用于判断异步链接结果
3. 做为IO线程,监听网络读操做位,负责从SocketChannel中读取报文
4. 做为IO线程,负责向SocketChannel写入报文发送给对方,若是发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据所有发送完成

以下图,是一个NioEventLoop的处理链:异步

  • 处理链中的处理方法是串行化执行的
  • 一个客户端链接只注册到一个NioEventLoop上,避免了多个IO线程并发操做

3.2.1 Task

Netty Reactor线程模型中有两种Task:系统Task和定时Task
  • 系统Task:建立它们的主要缘由是,当IO线程和用户线程都在操做同一个资源时,为了防止并发操做时锁的竞争问题,将用户线程封装为一个Task,在IO线程负责执行,实现局部无锁化
  • 定时Task:主要用于监控和检查等定时动做

基于以上缘由,NioEventLoop不是一个纯粹的IO线程,它还会负责用户线程的调度高并发

3.2.2 IO线程的分配细节

线程池对IO线程进行资源管理,是经过EventLoopGroup实现的。线程池平均分配channel到全部的线程(循环方式实现,不是100%准确),一个线程在同一时间只会处理一个通道的IO操做,这种方式能够确保咱们不须要关心同步问题。

3.2.3 Selector

NioEventLoop是Reactor的核心线程,那么它就就必须实现多路复用。

Selector的过程以下:

  • 首先oldWakenUp = wakenUp.getAndSet(false)
  • 若是队列中有任务, selectNow()
  • 若是没有select(),直达channel准备就绪,但此过程当中循环次数超过限值也将rebuidSelectoror退出循环
  • 执行processSelectedKeys和runAllTasks

epoll-bug的处理

在netty中对java nio的epoll bug进行了处理,就是设置一个阀值,若是超过了就rebuidSelector来避免epoll()死循环

3.2.4 NioEevntLoopGroup

EventExecutorGroup:提供管理EevntLoop的能力,他经过next()来为任务分配执行线程,同时也提供了shutdownGracefully这一优雅下线的接口

EventLoopGroup继承了EventExecutorGroup接口,并新添了3个方法

  • EventLoop next()
  • ChannelFuture register(Channel channel)
  • ChannelFuture register(Channel channel, ChannelPromise promise)

EventLoopGroup的实现中使用next().register(channel)来完成channel的注册,即将channel注册时就绑定了一个EventLoop,而后EvetLoop将channel注册到EventLoop的Selector上。

NioEventLoopGroup还有几点须要注意:

  • NioEventLoopGroup下默认的NioEventLoop个数为cpu核数 * 2,由于有不少的io处理
  • NioEventLoop和java的single线程池在5里差别变大了,它自己不负责线程的建立销毁,而是由外部传入的线程池管理
  • channel和EventLoop是绑定的,即一旦链接被分配到EventLoop,其相关的I/O、编解码、超时处理都在同一个EventLoop中,这样能够确保这些操做都是线程安全的
相关文章
相关标签/搜索