前言

对于超大规模并发的网络应用程序来说,可扩展性(Scalability)是支撑高并发的必要条件。 一方面需要保证基本功能的正确性,另一方面还需要保证程序的高性能。而不同的IO模型,对程序的性能影响是非常明显的。

为什么要使用Reactor

  • 传统阻塞IO模型的不足

    • 每个连接都需要独立线程处理,当并发数大时,创建线程数多,占用资源

    • 采用阻塞IO模型,连接建立后,若当前线程没有数据可读,线程会阻塞在读操作上,造成资源浪费

  • 针对传统阻塞IO模型的两个问题,可以采用如下的方案

    • 基于池化思想,避免为每个连接创建线程,连接完成后将业务处理交给线程池处理

    • 基于IO复用模型,多个连接共用同一个阻塞对象,不用等待所有的连接。遍历到有新数据可以处理时,操作系统会通知程序,线程跳出阻塞状态,进行业务逻辑处理

Reactor线程模型的思想就是基于IO复用和线程池的结合。其高效的原因在于网络I/O是耗时操作,将等待I/O数据就绪的过程抽象成为事件监听并用单独的线程处理,而具体的处理逻辑可以由其他业务线程处理,这样就不会阻塞业务线程从而增大了系统的吞吐量。缺点是提升了编码的复杂度

Netty 中三种 Reactor 线程模型

单线程模型

base-reactor-design该图描述了 Reactor 的单线程模型结构,在 Reactor 单线程模型中,所有 I/O 操作(包括连接建立、数据读写、事件分发等),都是由一个线程完成的。单线程模型逻辑简单,缺陷也十分明显:

  1. 一个线程支持处理的连接数非常有限,CPU 很容易打满,性能方面有明显瓶颈,对于多核资源机器来说是有点浪费的

  2. 当多个事件被同时触发时,只要有一个事件没有处理完,其他后面的事件就无法执行,这就会造成消息积压及请求超时

  3. 线程在处理 I/O 事件时,Select 无法同时处理连接建立、事件分发等操作

  4. 如果 I/O 线程一直处于满负荷状态,很可能造成服务端节点不可用

多线程模型

multithreaded-designs由于单线程模型有性能方面的瓶颈,多线程模型作为解决方案就应运而生了。Reactor 多线程模型将业务逻辑交给多个线程进行处理。除此之外,多线程模型其他的操作与单线程模型是类似的,例如读取数据依然保留了串行化的设计。当客户端有数据发送至服务端时,Select 会监听到可读事件,数据读取完毕后提交到业务线程池中并发处理,充分利用多核机器的资源、提高性能并且增加可靠性

该线程模型的不足,Reactor线程承担所有的事件,例如监听和响应,高并发场景下单线程存在性能问题

主从多线程模型


multiple-reactors这种模型下和第二种模型相比是把Reactor线程拆分了mainReactor和subReactor两个部分,每个 Reactor 线程都有独立的Selector对象。MainReactor 仅负责处理客户端连接的 Accept 事件,连接建立成功后将新创建的连接对象注册至 SubReactor。再由 SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件

Reactor三种模式形象比喻

饭店一般有接待员和服务员,接待员负责在门口接待顾客,服务员负责全程服务顾客,Reactor的三种线程模型可以用接待员和服务员类比

  1. 单Reactor单线程模型:接待员和服务员是同一个人,一直为顾客服务。客流量较少适合

  2. 单Reactor多线程模型:一个接待员,多个服务员。客流量大,一个人忙不过来,由专门的接待员在门口接待顾客,然后安排好桌子后,由一个服务员一直服务,一般每个服务员负责一片中的几张桌子

  3. 多Reactor多线程模型:多个接待员,多个服务员。这种就是客流量太大了,一个接待员忙不过来了

Netty 推荐使用主从多线程模型,这样就可以轻松达到成千上万规模的客户端连接。在海量客户端并发请求的场景下,主从多线程模式甚至可以适当增加 SubReactor 线程的数量,从而利用多核能力提升系统的吞吐量。

文章作者: 陆壹
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 陆壹笔记
Java 网络编程 netty reactor nio
喜欢就支持一下吧