从 Reactor 模式看 Netty、Redis 线程模型

从 Reactor 模式看 Netty、Redis 线程模型Reactor模式目前存在的线程模型有:传统阻塞I/O服务模型和Reactor模式。Reactor模式是基于事件驱动开发的,核心组成部分包括Reactor和线程池,其中Reactor负责监听和分发事件,线程池负责处理事件。Reactor分为三种模型:单线程模型(单Reactor单线程)多线程模型(单Reactor多线程)主从多线程模型(多Reactor多线程)单Reactor单线程Reactor和Handler都在同一个线程中执行,即s

大家好,欢迎来到IT知识分享网。


Reactor 模式

目前存在的线程模型有:传统阻塞 I/O 服务模型 和 Reactor 模式。

Reactor 模式是基于事件驱动开发的,核心组成部分包括 Reactor 和线程池,其中 Reactor 负责监听和分发事件,线程池负责处理事件。Reactor 分为三种模型:

  • 单线程模型 (单 Reactor 单线程)
  • 多线程模型 (单 Reactor 多线程)
  • 主从多线程模型 (多 Reactor 多线程)

单 Reactor 单线程

Reactor 和 Handler 都在同一个线程中执行,即 select 多路复用、事件分发和事件处理都是在同一个线程上完成的。既要接收客户端的连接请求,向服务端发起连接,又要发送/读取请求或应答/响应消息。

  • 优点:模型简单,没有多线程频繁切换、资源竞争的问题,全部都在一个线程中完成。
  • 缺点:性能问题:一个 NIO 线程同时处理成百上千的链路,存在性能问题,无法完全发挥多核 CPU 的性能,当 Handler在处理某个连接上的业务时,如果发生阻塞,其他所有 Handler 都得不到执行,将无法输入输出,也无法建立连接。
  • 缺点:可靠性问题:若线程进入死循环,整个程序不可用,对于高负载、大并发的应用场景不合适。
  • 使用场景:客户端的数量有限,业务处理非常快速。

在这里插入图片描述

  1. Select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求;
  2. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发;
  3. 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理;
  4. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应;
  5. Handler 会完成 Read → 业务处理 → Send 的完整业务流程

单 Reactor 多线程

一个 NIO 线程(Acceptor)只负责服务端监听,接收客户端的 TCP 连接请求;NIO 线程池负责网络 IO 的操作,即消息的读取、解码、编码和发送;1 个 NIO 线程可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或需要安全认证时,一个 Acceptor 线程可能会存在性能不足问题。

  • 优点:可以充分的利用多核 cpu 的处理能力。
  • 缺点:多线程数据共享和访问比较复杂。Reactor 承担所有的事件的监听和处理响应,它是单线程运行,在高并发场景容易出现性能瓶颈。也就是说 Reactor 主线程存在网络 IO 瓶颈。

在这里插入图片描述

  1. Reactor 对象通过 Select 监控客户端请求事件,收到事件后,通过 Dispatch 进行分发;
  2. 如果是建立连接请求,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 对象处理完成连接后的各种事件;
  3. 如果是处理请求,则由 Reactor 分发调用连接对应的 handler 来处理(也就是说连接已建立,后续客户端再来请求就基本是处理请求了,直接调用之前为这个连接创建好的 handler 来处理);
  4. handler 只负责读取数据、响应事件,不做具体的业务处理(这样不会使 handler 阻塞太久),通过 read 读取数据后,会分发给后面的 worker 线程池的某个线程处理业务,Reactor 线程可以处理其他事件。【业务处理是最费时的,所以将业务处理交给线程池去执行】;
  5. worker 线程池会分配独立线程完成真正的业务,并将结果返回给 handler
  6. handler 收到响应后,通过 send 将结果返回给 client

主从 Reactor 多线程

可以将 Reactor 线程拆分为多个子 Reactor 线程;同时引入多个 Selector 选择器,并为每一个 SubReactor 引入一个线程,一个线程负责一个选择器的事件轮询。

主 Reactor 线程用于绑定监听端口,接收客户端连接。当连接建立,将 SocketChannel 从主 Reactor 线程的多路复用器上移除,重新注册到 Sub 线程池的线程上, 用于处理 I/O 的读写等操作,从而保证 mainReactor 只负责接入认证、握手等操作。

  • 优点:父线程与子线程的数据交互简单,职责明确。父线程只需要接收新连接,之后交给子线程完成后续的业务处理。父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线程,子线程无需返回数据给主线程。
  • 缺点:编程复杂度较高。
    在这里插入图片描述
  1. Reactor 主线程 MainReactor 对象通过 select 监听连接事件,收到事件后,通过 Acceptor 处理连接事件;当 Acceptor 处理连接事件后,MainReactor 将连接分配给 SubReactor
  2. subReactor 将连接加入到连接队列进行监听,并创建 handler 进行各种事件处理;
  3. 当有新事件发生时,subReactor 就会调用对应的 handler 处理;
  4. handler 通过 read 读取数据,分发给后面的 worker 线程处理;
  5. worker 线程池分配独立的 worker 线程进行业务处理,并返回结果;
  6. handler 收到响应的结果后,再通过 send 将结果返回给 client
  7. Reactor 主线程可以对应多个 Reactor 子线程。

Netty 线程模型

Netty 主要基于主从 Reactor 多线程模型并做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor。内部实现了两个线程池,boss 线程池和 work 线程池,其中 boss 线程池的线程负责处理请求的 accept 事件,当接收到 accept 事件的请求时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 work 线程池。work 线程池负责轮询并处理 read 和 write 事件,由对应的 Handler 处理。

在这里插入图片描述

  1. BossGroup 线程维护 Selector,只关注 Accecpt
  2. 当接收到 Accept 事件,获取到对应的 SocketChannel,封装成 NIOScoketChannel 并注册到 Worker 线程(事件循环)进行维护,并创建对应 Handler 加入到通道;
  3. Worker 线程监听到 Selector 中通道发生自己感兴趣的事件后,就由 handler进行处理。

BossGroup 表示主Reactor 可以有多个(在 Netty 编程时我们通常会把 boss 线程池设置成单个线程),WorkerGroup 则代表 SubReactor 一样可以有多个(work 线程池则会直接使用默认的 CPU 核心数 * 2 个线程数)。

在这里插入图片描述

  1. Netty 抽象出两组线程池 ,BossGroup 负责接收客户端的连接,WorkerGroup 负责网络 IO 读写BossGroupWorkerGroup 类型都是 NioEventLoopGroup

  2. NioEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是 NioEventLoop

  3. NioEventLoop 表示一个不断循环的执行处理任务的线程,每个 NioEventLoop 都有一个 Selector,用于监听绑定在其上的 socket 的网络通讯;

  4. NioEventLoopGroup 可以有多个线程,即可以含有多个 NioEventLoop

  5. 每个 BossGroup下面的NioEventLoop 循环执行的步骤有 3 步:

    1. 轮询 accept 事件;
    2. 处理 accept 事件,与 client 建立连接,生成 NioScocketChannel,并将其注册到某个 workerGroup NIOEventLoop 上的 Selector
    3. 继续处理任务队列的任务,即 runAllTasks
  6. 每个 WorkerGroupNIOEventLoop 循环执行的步骤

    1. 轮询 readwrite 事件;
    2. 处理 I/O 事件,即 readwrite 事件,在对应 NioScocketChannel 处理;
    3. 处理任务队列的任务,即 runAllTasks
  7. 每个 WorkerNIOEventLoop 处理业务时,会使用 pipeline(管道),pipeline 中包含了 channel,即通过 pipeline 可以获取到对应通道,管道中维护了很多的处理器。

NioEventLoopGroup 下包含多个 NioEventLoop;每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue;每个 NioEventLoopSelector 上可以注册监听多个 NioChannel;每个 NioChannel 只会绑定在唯一的 NioEventLoop 上;每个请求对应一个 SocketChannel,每个 NioSocketChannel 都绑定有一个自己的 pipeline,里面包含一连串的 Handler

1、串行化设计避免线程竞争

Netty 采用串行化设计理念,从消息的读取->解码->处理->编码->发送,始终由单个 IO 线程 NioEventLoop 负责。整个流程不会进行线程上下文切换,数据无并发修改风险

一个NioEventLoop 聚合一个多路复用器 selector,因此可以处理多个客户端连接。

Netty 只负责提供和管理“IO线程”,其他的业务线程模型由用户自己集成。时间可控的简单业务建议直接在“IO线程”上处理,复杂和时间不可控的业务建议投递到业务线程池中处理

2、定时任务与时间轮

NioEventLoop中的Thread线程按照时间轮中的步骤不断循环执行:

  1. 在时间片Tirck内执行selector.select()轮询监听IO事件;
  2. 处理监听到的就绪IO事件;
  3. 执行任务队列taskQueue/delayTaskQueue中的非IO任务。

Redis 线程模型

Redis 基于 Reactor 模式开发了自己的文件事件处理器,由 4 个部分组成:多个 Socket 套接字、I/O 多路复用程序、文件事件分派器(dispatcher)、各种事件处理器

多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,IO 多路复用程序会监听多个 socket,将产生事件的 socket 放入队列中排队,文件事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。

Redis 使用 epoll 作为 I/O 多路复用技术的实现。并且,Redis自身的事件处理模型将 epoll 中的连接、读写、关闭都转换为事件,不在网络 I/O 上浪费过多的时间。

在这里插入图片描述
套接字:socket 连接,也就是客户端连接。当一个套接字准备好执行连接、写入、读取、关闭等操作时, 就会产生一个相应的文件事件。

I/O 多路复用程序:使用 epoll 实现,会根据当前系统自动选择最佳的方式。负责监听多个套接字,当套接字产生事件时,会向文件事件分派器传送那些产生了事件的套接字。当多个文件事件并发出现时, I/O 多路复用程序会将所有产生事件的套接字都放到一个队列里面,然后通过这个队列,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字:当上一个套接字产生的事件被处理完毕之后,才会继续传送下一个套接字。

文件事件分派器接收 I/O 多路复用程序传来的套接字, 并根据套接字的事件类型来为套接字关联不同的事件处理器

事件处理器:事件处理器就是一个个函数, 定义了某个事件发生时, 服务器应该执行的动作。例如:建立连接、命令查询、命令写入、连接关闭等等。包含连接应答处理器、命令请求处理器、命令回复处理器

Redis 的单线程

值得注意的是,Redis 正常处理客户端请求的核心流程,也就是执行命令阶段,一直都是单线程的。即使 Redis 6.0 再次引入了多线程,也主要是用于网络 IO 的,也就是接收命令和写回结果阶段,执行命令的核心流程依旧是单线程串行处理。

这也是 Redis 设计者的初衷,单线程编程更简单且容易维护,同时避免了线程的上下文切换以及死锁的问题,更重要的是无需使用加锁等方式进行线程同步,保证了数据安全

再比如说 AOF 重写也是这个道理,Redis 是 fork 一个子进程 bgrewriteaof 进行后台重写,而不是创建线程进行处理,因为如果使用的是线程,多线程之间会共享内存,这样修改共享数据时就需要加锁来保证数据安全,会降低性能。而子进程则一开始通过拷贝页表的方式与父进程共享物理内存,在发生共享数据修改时则会触发 Copy-On-Write 写时复制,于是父子进程就会有独立的数据副本,就不用加锁来保证数据安全。


正是因为 IO 多路复用模型使得服务器得以支撑百万级的并发连接。

参考资料:
https://xiaozhuanlan.com/topic/2153098467

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/23558.html

(0)
上一篇 2023-09-06 17:45
下一篇 2023-09-17 10:33

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信