高性能网络模型

一、Reactor模型

reactor设计模式是event-driven architecture的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js

大佬博客入口

这里说明多Reactor多线程的模式

1.多Reactor多线程的模式

image-20240524222756015

方案详细说明:

  • 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程
  • 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件
  • 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应
  • Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程

多 Reactor 多线程的方案虽然看起来复杂的,但是实际实现时比单 Reactor 多线程的方案要简单的多,原因如下:

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理
  • 主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端

上图各部分分析

  1. 主Reactor (MainReactor):
    • select:这表示主Reactor正在使用某种形式的I/O多路复用技术(如select、poll或epoll)来监听多个网络事件,主要是新的客户端连接请求。
    • dispatch:当主Reactor检测到新的连接请求时,它将分派(或分配)这些连接到合适的子Reactor。
    • Acceptor:这是一个特定的处理组件,负责接受新的连接请求。在accept操作之后,新的客户端连接被创建。
  2. 子Reactor (SubReactor):
    • 每个子Reactor都有自己的select和dispatch过程,用于管理它负责的客户端连接。
    • Handler:处理从子Reactor收到的事件。通常,一个Handler会负责读取数据(read)、进行业务处理,并将响应发送(send)回客户端。

工作流程

  • 客户端发起连接请求到服务器。
  • 主Reactor监听到新的连接请求,并通过Acceptor接受这个连接,然后创建一个用于通信的套接字文件描述符
  • 连接被分配到一个子Reactor,子Reactor将这个新的套接字文件描述符注册到自己的事件监听系统中
  • 当有数据到达时,子Reactor的select检测到事件,并通过dispatch将事件交给对应的Handler处理
  • Handler处理完业务逻辑后,读取数据,处理数据,并发送回客户端

工作原理

  1. 主Reactor:负责处理客户端的连接请求。主Reactor接收客户端的连接,并将新的连接分配给一个子Reactor。
  2. 子Reactor:每个子Reactor运行在自己的线程中,负责处理已经建立连接的客户端的IO事件,如读取数据、写入数据等。每个子Reactor都有自己的事件循环,可以独立处理事件。
  3. 工作线程池:当需要进行更复杂或耗时的业务处理时,子Reactor将任务提交给工作线程池中的线程进行处理,处理完成后通常由工作线程回写结果到客户端。
  4. 事件分发:主Reactor接收到新的连接请求后,根据某种策略(如轮询、最少连接数等)将连接分配给某个子Reactor处理,以平衡负载

(1)每一个Reactor均有一个自己的epoll实例

每一个子Reactor通常都会拥有属于自己的epoll实例。在多Reactor多线程模式中,这种设计允许每个子Reactor独立地管理和监控其负责的客户端连接的I/O事件。通过为每个子Reactor配备一个独立的epoll实例,可以实现并行处理,提升整个系统的效率和响应速度

子Reactor拥有独立的epoll实例的好处:

  1. 并行性:不同的子Reactor可以在不同的线程中运行,每个线程管理自己的epoll实例,这样可以充分利用多核CPU的并行处理能力。
  2. 效率提高:每个epoll实例只管理一部分客户端连接,减少了单个epoll实例需要处理的事件数量,这有助于减轻单个线程的负载,提高事件处理的效率。
  3. 隔离性:由于每个子Reactor管理自己的客户端连接,因此连接之间的干扰被最小化。一旦一个子Reactor遇到问题或者崩溃,它不会影响到其他子Reactor。
  4. 可扩展性:根据服务器的负载情况,可以动态地增加或减少子Reactor的数量。每个新的子Reactor可以简单地通过创建新的epoll实例来初始化,使得系统的扩展变得更加灵活

子Reactor使用epoll的工作流程

  1. 初始化:当子Reactor被创建时,它会初始化自己的epoll实例。这涉及调用epoll_create函数来创建一个epoll文件描述符,用于后续的事件监听和处理。
  2. 事件注册:子Reactor会将其管理的各个连接的文件描述符注册到它的epoll实例中。这通常通过epoll_ctl函数实现,指定要监听的事件类型(如读就绪、写就绪等)。
  3. 事件循环:子Reactor进入事件循环,调用epoll_wait函数等待事件的发生。一旦有事件发生,epoll实例会返回那些有事件发生的文件描述符,子Reactor随后根据事件类型进行相应的处理,如读取数据、发送数据等。
  4. 事件处理:子Reactor根据返回的事件进行具体的处理操作。如果操作涉及复杂或耗时的处理任务,可以将这些任务交给工作线程池,不阻塞事件循环

(2)主Reactor具体是怎样将用于通信的套接字文件描述符,分配给子Reactor

在多Reactor多线程模式中,主Reactor将新建立的客户端连接(即用于通信的套接字文件描述符)分配给子Reactor的过程是关键的一步,它影响到系统的负载均衡和整体性能。以下是几种常见的方法和策略,用于实现这一分配过程:

  1. 轮询方法

    轮询是最简单也是最常用的分配策略之一。主Reactor维护一个子Reactor的列表,并按顺序将新的连接逐一分配给列表中的每个子Reactor。这种方法尽量保证了每个子Reactor获得相同数量的连接,但不考虑各个子Reactor的当前负载情况

  2. 最少连接方法

    在这种策略中,主Reactor将新的连接分配给当前拥有最少活跃连接的子Reactor。这需要主Reactor跟踪每个子Reactor的连接数。这种方法试图更公平地分配负载,以优化资源利用和响应时间

  3. 随机分配

    随机分配是另一种简单的策略,主Reactor随机选择一个子Reactor来处理新的连接。这种方法的实现简单,但可能导致某些子Reactor承担更重的负载。

  4. 基于性能分配

    这种策略考虑到每个子Reactor的性能指标(如CPU使用率、响应时间等),将新连接分配给性能最佳的子Reactor。这种方法更为复杂,需要实时监控和评估子Reactor的性能状态

具体实现步骤

  1. 连接接受:主Reactor监听到新的客户端连接请求后,使用accept函数接受连接,从而获取一个新的套接字文件描述符。
  2. 选择子Reactor:根据上述某种策略,主Reactor决定将这个新连接分配给哪一个子Reactor。
  3. 传递文件描述符:主Reactor可以通过多种方式将套接字文件描述符传递给选定的子Reactor,常见的方法包括使用共享内存、线程安全队列或通过管道发送文件描述符。
  4. 注册事件监听:子Reactor接收到新的套接字文件描述符后,将其注册到自己的epoll实例中,开始监听和处理该连接的事件

(3)多Reactor多线程的模式下,有多少个子Reactor呢

子Reactor的数量没有一个固定的规定,它取决于应用程序的具体需求、系统的硬件资源(如CPU核心数)、以及预期的负载和性能目标。根据不同的场景和需求,开发者可以选择不同的策略来配置子Reactor的数量

确定子Reactor数量的几个关键因素

  1. 硬件资源:最基本的,子Reactor的数量通常与服务器的CPU核心数相关。通常情况下,为了充分利用多核性能,子Reactor的数量可以设置为与CPU核心数相等或略多。这样可以确保每个核心都能得到充分利用,同时避免过度的线程竞争和上下文切换。
  2. 客户端连接数:如果预期的客户端并发连接数非常高,可能需要更多的子Reactor来分散这些连接,以维持系统的响应性和稳定性。然而,过多的子Reactor可能导致管理复杂性增加和资源分配不均。
  3. 应用程序的I/O模式:如果应用程序涉及大量的短连接,可能需要更多的子Reactor来快速处理连接的建立和释放。相反,如果主要处理长连接,较少的子Reactor可能已足够,因为每个子Reactor可以维护较长时间的连接状态。
  4. 性能与可伸缩性测试:实际应用中,确定最优的子Reactor数量通常需要通过性能测试和可伸缩性分析来进行。通过模拟不同的负载情况,可以观察不同子Reactor配置下的系统表现,从而找到最佳平衡点

实践中的常见配置

  • 少量子Reactor:在不是非常高并发的应用中,可能只需要几个子Reactor(例如,与CPU核心数相等)。这种配置简单且易于管理。
  • 动态调整:在一些高级应用中,子Reactor的数量可能根据实时负载动态调整,以最优化资源使用和性能



二、Proactor模型

….