更多内容,前往个人博客

当说到 Netty 线程模型的时候,一般首先会想到经典的 Reactor 线程模型,尽管不同的 NIO 框架对于 Reactor 模式的实现存在差异,但本质上还是遵循了 Reactor 的基础线程模式。

一、Reactor 单线程模型


无论是C++ 还是 Java 编写的网络框架,大多数都是基于 Reactor 模型进行设计和开发,Reactor 模型基于事件驱动,特别适合海量的 I/O 事件。

【1】Reactor 单线程模型,是指所有的 I/O操作都是在同一个 NIO线程上面完成。NIO线程的职责如下(连接和消息应答):
   ■  作为 NIO服务端,接受客户端的 TCP连接;
   ■  作为 NIO客户端,向服务端发起 TCP连接;
   ■  读取通信对端的请求和应答消息;
   ■  向通信对端发送消息请求或者应答消息;

【2】Reactor 单线程模型如下图:

消息处理流程:1)、Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatcher 进行转发。
 2)、如果是连接事件,则由 acceptor接收连接,并创建 handler处理后续事件。
 3)、如果不是建立连接事件,则 Reactor会调用 Handler来响应。
 4)、handler 会完成 read、业务处理、send的完成业务流程。

Reactor 模型中的三种角色:
①、Reactor负责监听和分配事件,将 I/O事件分派给对应的Handler。新的事务包含连接建立就绪、读就绪、写就绪等。
②、Acceptor处理客户端新连接,并分派请求到处理器链中。
③、Handler将自身与事件绑定,执行非阻塞读写任务,完成 channel 的读入,完成处理业务逻辑后,负责将结果写出 channel。可以使用资源池来管理。
由于 Reactor 模式使用的是异步非阻塞的 I/O,所有的 I/O操作都不会阻塞,理论上一个线程可以独立处理所有的 I/O相关的操作。从架构层面看,一个 NIO线程确实可以完成其承担的职责。例如,通过 Acceptor 类接受客户端的 TCP连接请求消息,当链路建立成功之后,通过 Dispatch将对应的 ByteBuffer 派发到指定的 Handler上,进行消息解码。用户线程消息编码后通过 NIO线程将消息发送给客户端。

【3】在一些小容量的应用场景下,可以使用单线程模型。但是这对于高负载、大并发的应用场景不合适,主要原因如下:
   ●  一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100% ,也无法满足海量信息的编码、解码、读取和发送。
   ●  当 NIO 线程负载过重之后,处理速度将变慢,这会大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程的负载,最后会导致大量消息积压和处理超时,称为系统的性能瓶颈。
   ●  可靠性问题:一旦 NIO线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了 Reactor 多线程模型,接下来就看看 Reactor 多线程模型。

二、Reactor 多线程模型


【1】与单线程模型最大的区别就是有一组 NIO 线程来处理 I/O 操作,它的原理图如下:

消息处理流程:1)、Reactor 对象通过 Selector监听客户端请求事件,收到事件后通过 Dispatcher进行分发。
 2)、如果是建立连接请求,则由 acceptor通过 accept处理连接请求,然后创建一个 Handler对象处理连接完成后续的各种事件。
 3)、如果不是连接请求,则 Reactor 会分发给调用连接对应的 Handler 来响应。
 4)、Handler 只负责响应事件,不做具体业务处理,通过 Read读取数据后,会分发给后面 Worker 线程池进行业务处理。
 5)、Worker线程池分配独立的线程完成真正的业务处理,将响应结果发送给 Handler进行处理。
 6)、Handler 收到响应结果后,通过 send将响应结果返回给客户端。
【2】Reactor 多线程模型的特点如下:
   ■  有专门一个 NIO 线程:Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求。
   ■  网络IO 操作:读写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。
   ■  一个 NIO 线程可以同时处理 N 条链路,但是一个链路只对应一个 NIO 线程,防止发生并发操作问题。
【3】大多数情况下,Reactor 多线程模型可以满足性能需求,但是,在个别特殊场景中,一个NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个 Acceptor 线程可能会存在性能不足的问题,为了解决性能问题,产生了第三种 Reactor 线程模型——主从 Reactor 线程模型。

三、主从 Reactor 多线程模型


【1】主从 Reactor 线程模型的特点是:服务端用于接收客户端连接的不再是一个单独的 NIO 线程,而是一个独立的 NIO 线程池。Acceptor 接受客户端 TCP 连接请求并处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 I/O 线程池(Sub reactor线程池)的某个 I/O 线程上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池不仅仅用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 I/O 线程上,由 I/O 线程负责后续的I/O 操作。
【2】主从 Reactor 线程模型原理图:

消息处理流程:1)、从 Acceptor Pool(主线程池 boss)中随机选择一个 Reactor 线程作为 acceptor 线程,用于绑定监听端口,接收客户端连接。
 2)、acceptor 线程接收客户端连接请求之后创建新的 SocketChannel,将其注册到主线程池的其他 Reactor 线程上,由其负责接入认证、握手、黑白名单和登录等操作。
 3)、第二步完成之后,业务层的链路正式建立,将 SocketChannel 从主线程池的Reactor线程的多路复用器上摘除,重新注册到工作线程池(Sub),并创建一个 Handler 用于处理各种连接事件。
 4)、当有新的事件发生时,subReactor 会调用连接对应的 Handler 进行响应。
 5)、Handler 通过 Read 读取数据后,会分发给后面的 Worker 线程池进行处理。
 6)、Worker 线程池会分配独立的线程完成真正的业务处理,如果有响应结果则发给 Handler 进行处理。
 7)、Handler 收到响应结果后通过 Send 将响应返回给 Client(结束)。
【3】利用主从 Reactor 线程模型,可以解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题。因此,在 Netty 的官方 Demo 中,推荐使用该线程模型。

四、Netty 的线程模型


Netty 的线程模型并不是一成不变的,它实际取决于用户的启动参数配置。通过设置不同的启动参数,Netty 可以同时支持 Reactor 单线程模型、多线程模型和主从 Reactor 多线程模型。

【1】Netty 的线程模型如下:

【2】可以通过如下 Netty 服务端启动代码来了解它的线程模型。

1 EventLoopGroup bossGroup = new NioEventLoopGroup();
2 EventLoopGroup workerGroup = new NioEventLoopGroup();
3 try {
4 ServerBootstrap bootstrap = new ServerBootstrap();
5 bootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
6 .childHandler(new PersonChannelInitializer());

服务端启动时创建两个 NioEventLoopGroup,它们实际上是两个独立的 Reactor 线程池。一个用于接收客户端的 TCP 连接,另一个用于处理 I/O 相关的读写操作,或者执行系统 Task、定时任务 Task 等。
【3】Netty 用于接收客户端请求的线程池职责如下:
   ■  接收客户端 TCP 连接,初始化 Channel 参数。
   ■  将链路状态变更事件通知给 ChannelPipeline。
【4】Netty 处理 I/O 操作的 Reactor 线程池职责如下:
   ■  异步读取通信对端的数据报,发送读事件到 ChannelPipeline;
   ■  异步发送消息到通信对端,调用 ChannelPipeline 的消息发送接口;
   ■  执行系统调用 Task;
   ■  执行定时任务 Task,例如链路空闲状态监测定时任务。
通过调整线程池的线程个数,是否共享线程池等方式,Netty 的 Reactor 线程模型可以在单线程、多线程和主从多线程间切换,这种灵活的配置方式可以最大程度地满足不同用户的个性定制。
为了尽可能的提升性能,Netty 在很多地方进行了无锁化设计,例如在 I/O 线程内部进行串行操作,避免多线程竞争导致的性能下降问题。表面上看,串行化设计似乎 CPU 利用率不高,并发程度不够。但是,通过调整 NIO 线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列,多个工作线程的模型性能更优。设计原理如下图所示:

Netty 的 NioEventLoop 读取到消息后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg)。只要用户不主动切换线程,一直都是 NioEventLoop 调用用户的 Handler,期间不进行线程切换。这种串行处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。

五、最佳实践


【1】Netty 的多线程编程最佳实践如下:
   1)、创建两个 NioEventLoopGroup,用于逻辑隔离 NIO Acceptor 和 NIO I/O 线程。
   2)、尽量不要在 ChannelHandler 中启动用户线程(解码后用于将 POJO 消息派发到后端业务线程除外)。
   3)、解码要放在 NIO 线程调用的解码 Handler 中进行,不要切换到用户线程中完成消息的解码。
   4)、如果业务逻辑操作非常简单、没有复杂的业务逻辑计算,没有可能会导致线程被阻塞的磁盘操作、数据库操作、网络操作等,可以直接在 NIO 线程上完成业务逻辑编排,不需要切换到用户线程。
   5)、如果业务逻辑处理复杂,不要在 NIO 线程上完成,建议将编解码后的 POJO 消息封装成 Task 任务,派发到业务线程池中由业务线程执行,以保证尽快被释放,处理其他的 I/O 操作。
【2】推荐的线程数量计算公式有以下两种:
   ■  线程数量 = (线程总时间/瓶颈资源时间)* 瓶颈资源的线程并行数。
   ■  QPS(每秒查询率) = 1000/线程总时间 * 线程数。
由于用户场景的不同,对于一些负责的系统,实际上很难计算出最优线程配置,只能是根据测试数据和用户场景,结合公式给出一个相对合理的范围,然后对范围内的数据进行性能测试,选择相对最优值。

Netty 线程模型(Reactor 线程模型)的更多相关文章

  1. Netty高性能之Reactor线程模型

    Netty是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用 ...

  2. 【Netty源码分析】Reactor线程模型

    1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...

  3. Netty Reactor 线程模型笔记

    引用: https://www.cnblogs.com/TomSnail/p/6158249.html https://www.cnblogs.com/heavenhome/articles/6554 ...

  4. Reactor 线程模型以及在netty中的应用

    这里我们需要理解的一点是Reactor线程模型是基于同步非阻塞IO实现的.对于异步非阻塞IO的实现是Proactor模型. 一 Reactor 单线程模型 Reactor单线程模型就是指所有的IO操作 ...

  5. 深入Netty逻辑架构,从Reactor线程模型开始

    本文是Netty系列第6篇 上一篇文章我们从一个Netty的使用Demo,了解了用Netty构建一个Server服务端应用的基本方式.并且从这个Demo出发,简述了Netty的逻辑架构,并对Chann ...

  6. Netty源码分析之Reactor线程模型详解

    上一篇文章,分析了Netty服务端启动的初始化过程,今天我们来分析一下Netty中的Reactor线程模型 在分析源码之前,我们先分析,哪些地方用到了EventLoop? NioServerSocke ...

  7. Netty源码解析一——线程池模型之线程池NioEventLoopGroup

    本文基础是需要有Netty的使用经验,如果没有编码经验,可以参考官网给的例子:https://netty.io/wiki/user-guide-for-4.x.html.另外本文也是针对的是Netty ...

  8. 网络编程NIO之Reactor线程模型

    目录 单Reactor线程模型 基于工作线程的Reactor线程模型 多Reactor线程模型 多Reactor线程模型示例 结束语 上篇文章中写了一些NIO相关的知识以及简单的NIO实现示例,但是示 ...

  9. netty源码分析之揭开reactor线程的面纱(一)

    netty最核心的就是reactor线程,对应项目中使用广泛的NioEventLoop,那么NioEventLoop里面到底在干些什么事?netty是如何保证事件循环的高效轮询和任务的及时执行?又是如 ...

  10. 一文聊透 Netty 核心引擎 Reactor 的运转架构

    本系列Netty源码解析文章基于 4.1.56.Final版本 本文笔者来为大家介绍下Netty的核心引擎Reactor的运转架构,希望通过本文的介绍能够让大家对Reactor是如何驱动着整个Nett ...

随机推荐

  1. B - WeirdSort

    B - WeirdSort 思路:经过认真的审题,你会发现,这只是个冒泡的变形,我们建立两个数组,然后用一个数组里面的数字确定位置,然后冒泡就行了.最后抖机灵用了个is_sorted,判断数组里面数字 ...

  2. c二级

    一·基本数据结构与算法 算法基本概念 算法:解决问题的方法 程序:用某种语言来诠释算法,将算法写成代码. 算法基本特征: 1.可行性 2.确定性 3.有穷性 4.有足够的情报 算法的基本要素 1.算法 ...

  3. modbus_tk Rru

    import serialimport structimport loggingimport modbus_tkimport modbus_tk.defines as cstimport modbus ...

  4. Jmeter四、jmeter脚本组成和组件搭配

    一.jmeter脚本开发原则 简单:去除无关的组件,同时能复用的尽量复用. 正确:对脚本或者业务正确性进行必要的判断,不能少也不能多(200) 高效:部分组件仅仅在脚本开发模式使用,在真正生产环境下不 ...

  5. datetime 获取当前时间的各种格式(转)

    我们可以通过使用DataTime这个类来获取当前的时间.通过调用类中的各种方法我们可以获取不同的时间:如:日期(2008-09-04).时间(12:12:12).日期+时间(2008-09-04 12 ...

  6. C++ 复习函数的基本知识

    C++ 复习函数的基本知识 要使用 C++ 函数,必须完成如下工作: 1. 提供函数定义: 2. 提供函数原型: 3. 调用函数. 例子: #include <iostream> usin ...

  7. 数组(Java)

    数组的定义 数组是相同类型数据的有序集合 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成 其中,每个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们 数组的声明和创建 基本 ...

  8. XDZX2022学生博客链接

    王桐林博客        https://www.cnblogs.com/xdzxtong/ 邢沐辰博客 https://www.cnblogs.com/xdzxmuchen/ 袁 博博客 https ...

  9. linux 命令下载文件

    系统  ubuntu  :若提示没有找到命令 请自行下截安装 sz 命令发送文件到本地:# sz filename rz命令本地上传文件到服务器:# rz执行该命令后,在弹出框中选择要上传的文件即可.

  10. IndexError: invalid index of a 0-dim tensor. Use tensor.item() to convert a 0-dim tensor to a Python number

    print('Epoch[{}/{}], loss:{:.6f}'.format(epoch+1,num_epoch,loss.data[0])) 将loss.data[0] 改为loss.item( ...