type
status
date
slug
summary
tags
category
password

1、Reactor 模型

Reactor 线程模型是 Netty 组件设计和线程模型的基础,在这里先介绍一下。Reactor 模型是一个 IO 设计模式,Java 中的 NIO 就对 Reactor 模式提供了很好的支持,比较著名的就是 Doung Lea 大神在 《Scalable IO in Java》演示如何使用 NIO 实现 Reactor 模式。
在维基百科上对 Reactor 模式定义如下:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers
从这段定义我们可以得到三个关键点:
  • 事件驱动(event handling)
  • 可以处理一个或多个输入源(one or more inputs)
  • 通过 Service Handler 同步的将输入事件(Event)采用多路复用分发给相应的 Request Handler(多个)处理
基本流程图如下:
notion image
对流程图进行进一步抽象,得到 Reactor 模型的 OMT 类图如下:
notion image
解释如下:
  • Handle(描述符):表示触发事件,是触发所有操作的发源地。在 Linux 中 Handle 就是文件描述符 fd。
  • Synchronous Event Demultiplexer(同步事件分离器):事件发生后通知 Initiation Dispatcher,对于 Linux 来说,同步事件分离器指的就是常用的 I/O 多路复用机制,比如说 select、poll、epoll 等。在 Java NIO 领域中,同步事件分离器对应的组件就是 Selector,对应的阻塞方法就是 select 方法。
  • Initiation Dispatcher(初始分发器):相当于 Reactor 的角色,Initiation Dispatcher 会通过 Synchronous Event Demultiplexer 来等待事件的发生。一旦事件发生,Initiation Dispatcher 会调用 Event Handler 来处理事件。
  • Event Handler(事件处理器):事件产生时实现相应的回调方法进行业务逻辑,类似于接口。
  • Concrete Event Handler(具体事件处理器): Event Handler的实现。
对 OMT 类图进行简化后,Reactor 模型定义了三种角色:
  • Reactor: 负责监听和响应事件,将事件分发绑定了该事件的 Handler 处理
  • Handler: 事件处理器,绑定了某类事件,负责对事件进行处理
  • Acceptor:Handler 的一种,绑定了 connect 事件,当客户端发起 connect 请求时,Reactor 会将 accept 事件分发给 Acceptor 处理
这三种角色也是我们下面介绍的三种模型的基础。

1.1 Reactor单线程模型

notion image
处理流程
  1. Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发
  1. 如果是连接建立的事件,则交由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理
  1. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应
  1. Handler 会完成 read -> 业务处理 -> send 的完整业务流程
特点分析
  • 优点:模型简单,没有多线程,进程通信,竞争的问题,全部都在一个线程中完成。Redis 就是使用 Reactor 单进程的模型。
  • 缺点:所有的 IO 操作(read、send)和非 IO 操作(decode、compute、encode)都在一个线程里面完成,当非 IO 操作处理速度较慢时,会导致 IO 响应速度严重下降。

1.2 Reactor多线程模型

notion image
处理流程
  1. 主线程中,Reactor 对象通过 select 监听连接事件,收到事件后通过 dispatch 进行分发。
  1. 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
  1. 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler 来进行相应。
  1. Handler 只负责请求响应事件,不进行业务处理。Handler 通过 read 读取到数据后,会发给业务线程池 Thread Pool 进行业务处理。
  1. Thread Pool 会在独立的子线程中完成真正的业务处理,然后将响应结果返回给 Handler。
  1. Handler 收到响应后通过 send 将响应结果返回给 client。
特点分析
  • 优点:和单线程模型相比,多线程模型最大的特点就是把非 IO 操作(decode、compute、encode)抽取出来放到专门的业务线程池去进行处理,Handler 只需要负责 IO 操作(read、send),这样会大大提升系统的 IO 响应速度。
  • 缺点:这个模型仍然把管理连接的 acceptor 和负责 IO 的 Handler 放在同一个 Reactor 中。在瞬间高并发的场景中,系统可能会因为忙于处理新的连接,导致 IO 响应速度瞬间下降。

1.3 主从Reactor多线程模型

notion image
处理流程
  1. 主进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给子进程 subReactor(可以有多个)。
  1. 子进程中的 subReactor 将 mainReactor 分配的连接加入队列进行监听,并把该连接从 mainReactor 的管理队列中移除,然后创建一个 Handler 用于处理连接的各种事件
  1. 当有新的事件发生时,subReactor 会调用里连接对应的 Handler 来进行处理。
  1. Handler 通过 Read 读取数据后,会分发给后面的 Thread Pool 线程池进行业务处理。
  1. Thread Pool 线程池会分配独立的线程完成真正的业务处理,然后将响应结果返回给 Handler。
  1. Handler 收到响应结果后通过 send 将响应结果返回给 client。
特点分析
和多线程模型相比,该模型将 Reactor 分成两部分,mainReactor 只负责管理连接(accept),subReactor 负责管理除了连接外的其他操作(read、decode、compute、encode、send)。mainReactor 和 subReactor 相互独立,之间的交互非常简单,mainReactor 只需要把连接传给 subReactor 就完成任务了。Reactor 具有以下特点:
  • 响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的。
  • 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销。
  • 可扩展性,可以方便地通过增加 Reactor 实例个数来充分利用 CPU 资源。
  • 可复用性,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性。

1.4 Netty 线程模型

Netty 线程模型采用了主从 Reactor 多线程模型,Netty 的主要组件都是围绕该模型进行设计的。支持以下几种实现:
  1. 单线程模型
      • 所有I/O操作由一个线程处理
      • 适用于并发连接数少的场景
  1. 多线程模型
      • 一个 Acceptor 线程接收连接
      • 一组 Worker 线程处理I/O操作
      • 适用于大多数场景
  1. 主从多线程模型
      • 主 Reactor 线程组处理连接请求
      • 从 Reactor 线程组处理I/O操作
      • 适用于高并发场景
Netty Server 端的主从多线程模型为例:
notion image
  • Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup
  • 每个 Boss NioEventLoopGroup 通常包含 1 个 NioEventLoop,1 个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。Boss NioEventLoopGroup 的工作过程如下:
    • 轮询 Accept 事件。
    • 处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoopSelector 上。
    • 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。
  • 每个 Worker NioEventLoopGroup 通常包含多个 NioEventLoopWorker NioEventLoopGroup 的工作过程如下:
    • 轮询 ReadWrite 事件。
    • 处理 I/O 事件,即 ReadWrite 事件,在 NioSocketChannel 可读、可写事件发生时进行处理。
    • 处理任务队列中的任务,runAllTasks。

2、Netty 组件

2.1 Netty核心组件

Netty 核心组件:
  • Channel代表一个到实体(如硬件设备、文件、网络套接字)的开放连接。Netty 的基础组件,可以把他理解为 BIO 的 Socket 类和 NIO 的 Channel 的升级版,把相关 API 进行进一步封装,提供了基本的 I/O 操作如 bind、connect、read、write 等。用 Netty 编写网络程序的时候,我们一般不会直接操纵 Channel,而是直接操作 Channel 的相关组件例如ChannelHandler。
  • ChannelHandler处理入站和出站事件,例如连接、接收、数据转换、异常处理以及我们的业务逻辑逻辑。一个 Channel 可以包含多个 ChannelHandler,这些 ChannelHandler 呈流水线式处理事件。一个 ChannelHandler 也可以被多个 Channel 复用。ChannelHandler 包括两个核心子类:
    • ChannelInboundHandler:用于接收、处理入站数据和事件
    • ChannelOutboundHandler:用于接收、处理出站数据和事件
  • ChannelPipelineChannelHandler 的链表,负责把多个 ChannelHandler 串行起来,定义了处理入站和出站事件的流程。一个 Channel 包含一个 ChannelPipline。当一个数据流进入 ChannlePipeline 时,它会从 ChannelPipeline 头部开始传给第一个 ChannelInboundHandler ,当第一个处理完后再传给下一个,一直传递到管道的尾部。与之相对应的是,当数据被写出时,它会从管道的尾部开始,先经过管道尾部的 “最后” 一个ChannelOutboundHandler,当它处理完成后会传递给前一个 ChannelOutboundHandler 。
  • ChannelHandlerContext包含着 ChannelHandler 中的上下文信息,和 ChannelPipeline 的前后 ChannelHandler 进行信息交互。当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext,借助 ChannelHandlerContext 来实现和同一个 ChannelPipeline 的前后 ChannelHandler 进行上下文传递。
  • ChannelFuture异步 IO 操作的结果,可以添加监听器在操作完成时得到通知。在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息的处理处理。这时候我们通过 ChannelFuture 或 ChannelPromise(ChannelFuture 的扩展,可以设置结果),对结果注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。
  • EventLoop事件循环,负责处理 I/O 事件、用户任务和定时任务。一个 EventLoop 对应一个线程,本质是一个无限 while 循环,单线程顺序执行所有操作。每个 Channel 在生命周期内仅注册到一个 EventLoop,而一个 EventLoop 可以绑定一到多个 Channel,Channel 的所有 I/O 操作均由绑定的 EventLoop 线程处理。
  • EventLoopGroup可以理解为 EventLoop 的线程池,包含一到多个 EventLoop。
其他重要组件:
  • Bootstrap客户端/服务端的配置启动类。一个 Netty 应用通常由一个 Bootstrap 开始,作用是配置参数、串联各个组件和启动应用程序。Bootstrap 分为 Bootstrap 和 ServerBootstrap。
    • Bootstrap 是客户端程序的启动引导类,客户端启动后连接远程主机。
    • ServerBootstrap 是服务端启动引导类,服务端启动后等待客户端连接。
  • ByteBufNetty 的数据容器,本质是一个由不同索引分别控制读访问和写访问的字节数组。Netty 的 ByteBuf 对 NIO 的 ByteBuffer 进行封装,提供更友好的 api 和更强大的功能。支持池化、引用计数。
  • Codec编码解码器,用于处理字节流与消息对象之间转换,Netty 提供了多种内置编解码器。

2.2 Netty组件关系

  • 一个 EventLoopGroup 包含一个或多个 EventLoop。
  • 一个 EventLoop 在它的生命周期内只能与一个 Thread 绑定,所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理。
  • 一个 Channel 会注册到一个 EventLoop 上,然后在它的整个生命周期过程中,都会用这个 EventLoop。
  • 一个 EventLoop 可被分配至一个或多个 Channel 。
  • 一个 Channel 对应一个 ChannelPipline。
  • 一个 ChannelPipline 包含多个 ChannelHandler。
  • 每个 ChannelHandler 对应一个 ChannelHandlerContext,与前后 ChannelHandler 的 ChannelHandlerContext 产生关联。
notion image

2.3 Netty的工作流程

  1. 启动流程
    1. 创建 EventLoopGroup(Boss 和 Worker)
    2. 创建 ServerBootstrap 并配置参数
    3. 绑定端口,启动服务
  1. 请求处理流程
    1. 连接建立
        • Boss EventLoop 接受连接
        • 创建 Channel 并注册到 Worker EventLoop
    2. 事件处理
        • Worker EventLoop 监听 Channel 事件(读、写等)
        • 事件触发后,通过 ChannelPipeline 传播
        • 依次调用注册的 ChannelHandler 处理
    3. 数据处理
        • 解码器(Decoder)将字节流转换为消息对象
        • 业务 Handler 处理业务逻辑
        • 编码器(Encoder)将响应消息转换为字节流
        • 通过 Channel 发送响应
Netty系列(四):BootstrapNetty系列(二):Netty入门
Loading...