Netty实战与源码剖析(二)——Netty线程模型
1 线程模型基本介绍
不同的线程模式,对于程序的性能有很大的影响,Netty为何具有如此高的性能,很大程度上是得益于Netty采用的线程模型。
目前主流存在的线程模型有两种:
- 传统阻塞IO模型
- Reactor模型
然而根据Reactor的数量和处理资源线程的数量不同,又可以将Reactor模型分为三种:
- 单Reactor 单线程
- 单Reactor 多线程
- 主从Reactor 多线程
Netty基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor。
1.1 传统阻塞IO模型
工作原理图:
模型特点:
- 采用阻塞IO模式获取输入的数据
- 每个连接都要独立的线程来完成数据的输入、业务处理、数据的输出返回
问题分析:
- 当并发度很大,就会创建大量的线程,占用很大的系统开销
- 当连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源的浪费
解决方案:
- 基于IO复用模型:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有数据可用时,操作系统通知应用程序,线程从阻塞状态返回,处理业务逻辑。
- 基于线程池复用线程资源:不必再为每个连接创建一个线程,将连接完成后的业务处理交给线程,这样可以使得线程池中的每一个线程都可以处理多个连接的业务逻辑。
1.2 Reactor模式
上述说的IO复用模型结合线程池机制,就形成了Reactor模式。
工作原理图:
模型特点:
- Reactor模式是通过一个或多个输入同时传递给服务处理器的模式,基于事件驱动
- 服务端程序处理连接的多个请求,并把请求分派给相应的处理线程,故Reactor模式也叫Dispatcher模式
- Reactor模式使用IO复用模型,监听事件,收到事件后,分发给某个线程或进程,这也是网络服务器高并发处理的关键
核心组件:
- Reactor:其实就是上图中的ServiceHandler
- Handler:其实就是上图中处理线程的EventHandler
1.3 单Reactor单线程模式
前面的博客中,Java NIO编程其实就是一种单Reactor单线程,Selector相当于Reactor,得到SelectKey后的操作其实就是在一个线程中完成的,这就是一个Handler。
- 优点:模型简单,无多线程、线程通信、上下文切换、锁竞争等问题
- 缺点
- 性能低,只用了一个线程,无法完全发挥多核CPU的性能
- 可用性低,线程若意外终止,或进入死循环,导致整个通信模块不可用
- 使用场景:客户端数量有限,业务处理快速等
1.4 单Reactor多线程模式
和单Reactor单线程模式比较类似,不同的地方在于,当Reactor收到事件,如果是连接事件,则交给一个特定的线程处理连接后的业务;如果不是连接时间,则由Reactor分发到不同的Handler,但Handler只负责相应事件,不做具体的业务处理,只是通过read读取数据后,交给worker线程池进行处理业务,worker线程池会分配一个线程来处理业务,并将结果返回给Handler,Handler再返回结果给客户端。
- 优点:充分利用多核CPU的处理能力
- 缺点
- 多线程数据共享和访问较为复杂,线程安全问题;
- Reactor是单线程中运行的,处理所有的事件监听和相应,高并发下有性能瓶颈
1.5 主从Reactor多线程模式
Reactor主线程MainReactor对象通过select监听连接事件,收到连接事件后,则交给一个特定的线程处理连接后的业务;如果是其他事件,MainReactor会将事件分配给从线程的SubReactor对象,然后SubReactor将事件分发到不同的Handler,但Handler只负责相应事件,不做具体的业务处理,只是通过read读取数据后,交给worker线程池进行处理业务,worker线程池会分配一个线程来处理业务,并将结果返回给Handler,Handler再返回结果给客户端。
- 优点:父线程与子线程的数据交互简单,职责明确,父线程只需要接受新连接,子线程则完成后续的业务逻辑处理;父子线程数据交互简单,主线程只需要把非连接的事件传递给子线程,子线程无需返回数据
- 缺点:复杂度高
- 这种模型在许多项目中广泛使用,包括Nginx主从Reactor多进程模型,Memcached主从多线程,Netty主从多线程模型的支持。
2 Netty线程模型
如同所示:
-
Netty 抽象出两组线程池:BossGroup 和 WorkerGroup,也可以叫做 BossNioEventLoopGroup 和 WorkerNioEventLoopGroup。每个线程池中都有 NioEventLoop 线程。BossGroup 中的线程专门负责和客户端建立连接,WorkerGroup 中的线程专门负责处理连接上的读写。BossGroup 和 WorkerGroup 的类型都是 NioEventLoopGroup。
-
NioEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个 NioEventLoop。
-
NioEventLoop 表示一个不断循环的执行事件处理的线程,每个 NioEventLoop 都包含一个 Selector,用于监听注册在其上的 Socket 网络连接(Channel)。
-
NioEventLoopGroup 可以含有多个线程,即可以含有多个 NioEventLoop。
-
每个 BossNioEventLoop 中循环执行以下三个步骤:
- select:轮训注册在其上的 ServerSocketChannel 的 accept 事件(OP_ACCEPT 事件)
- processSelectedKeys:处理 accept 事件,与客户端建立连接,生成一个 NioSocketChannel,并将其注册到某个 WorkerNioEventLoop 上的 Selector 上
- runAllTasks:再去以此循环处理任务队列中的其他任务
-
每个 WorkerNioEventLoop 中循环执行以下三个步骤:
- select:轮训注册在其上的 NioSocketChannel 的 read/write 事件(OP_READ/OP_WRITE 事件)
- processSelectedKeys:在对应的 NioSocketChannel 上处理 read/write 事件
- runAllTasks:再去以此循环处理任务队列中的其他任务
-
在以上两个processSelectedKeys步骤中,会使用 Pipeline(管道),Pipeline 中引用了 Channel,即通过 Pipeline 可以获取到对应的 Channel,Pipeline 中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。