• 异步事件驱动的 NIO 框架,基于 Java NIO 提供的API实现
  • 所有的 I/O 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便地主动获取或通过通知机制获取 I/O 操作结果

Reactor 线程模型

  • 服务端在接收到客户端请求后采用多路复用策略,通过一个非阻塞的线程来异步接收所有的客户端请求,并将这些请求转发到相关的工作线程组上进行处理

    Java NIO

    三个核心概念: Selector(选择器)、Channel(通道)、Buffer(缓冲区)

  • Selector 用于监听多个 Channel 的事件

    • 传统的 I/O 基于数据流进行 I/O 读写,而 Java NIO 基于 Channel 和 Buffer 进行 I/O 读写操作,并且数据总是从 Channel 读到 buffer,或从 Buffer 写入 Channel
    • Selector 只在 Channel 上有读写事件发生时,才会调用 I/O 函数进行读写操作,可极大地减少系统开销,提高并发量
  • Channel
    • 数据流管道
    • FileChannel:文件的 I/O 操作
    • DatagramChannel:UDP 广播事件
    • SocketChannel:Socket 客户端 TCP 的读写
    • ServerSocketChannel: Socket 服务端的 TCP 的读写
  • Buffer

    • 容器,其内部通过一个连续的字节数组存储 I/O 上的数据
    • 在 NIO 中,Channel 对数据的读写都必须经过 Buffer

      线程模型

      Reactor 单线程模型
      Accept 接收客户端的 TCP 连接请求消息
      链路建立成功后通过 Dispatcher 将接收到的消息写入 ByteBuffer,并派发到对应的 DecoderHandler进行解码和处理
      消息处理完成后调用对应的EncoderHandler将请求响应进行消息的编辑和下发
      原理 - 图1
      Reactor 多线程模型
      用于接收客户端请求的 Acceptor 由一个线程负责,用户处理客户端消息的 Dispatcher 由一个线程池负责
      原理 - 图2
      Reactor 主从多线程模型

      核心组件

  • Bootstrap/ServerBootstrap:Bootstrap 用于客户端服务的启动引导,ServerBootstrap 用于服务端服务的启动引导

  • NioEventLoop:基于线程队列的方式执行事件操作,具体要执行的事件操作包括连接注册、端口绑定和I/O数据读写等。每个NioEventLoop线程都负责多个Channel的事件处理
  • NioEventLoopGroup:NioEventLoop生命周期的管理
  • Future/ChannelFuture:Future和ChannelFuture用于异步通信的实现,基于异步通信方式可以在I/O操作出发后注册一个监听事件,在I/O操作完成后自动触发监听事件并完成后续操作
  • Channel:用于执行具体的 I/O操作
  • Selector:用于多路复用中 Channel 的管理,在内部监听每个Channel上 I/O 事件的变化,当 Channel 有网络I/O事件发生时通知 ChannelHandler 执行具体的 I/O 操作
  • ChannelHandler:I/O事件的拦截和处理。分为ChannelInboundHandler 和 ChannelOutboundHandler
  • ChannelHandlerContext:Channel 上下文信息的管理。每个 ChannelHandler都对应一个ChannelHandlerContext
  • ChannelPipeline:基于拦截器设计模式实现的事件拦截处理和转发。每个 Channel 都对应一个 ChannelPipeline,在ChannelPipeline中维护了一个由 ChannelHandlerContext 组成的双向链表,每个 ChannelHandlerContext 都对应一个 ChannelHandler,以完成具体 Channel 事件的拦截和处理

    原理

    Netty 的运行核心包含两个 NioEventLoopGroup 工作组
    一个是 BossGroup,用于接收客户端连接、接收客户端数据和进行数据转发
    一个是 WorkerGroup,用于具体的 I/O 事件的触发和数据处理

    服务端的初始化步骤

  1. 初始化 BossGroup 和 WorkerGroup
  2. 基于 ServerBootstrap 配置 EventLoopGroup,包括连接参数设置、Channel 类型设置、编解码 Handler 设置等
  3. 绑定端口和服务启动

    BossGroup 职责

    一个事件循环组,其中包含多个事件循环(NioEventLoop),每个 NioEventLoop都包含一个Selector和一个 TaskQueue(事件循环线程)
  • 每个 Boss NioEventLoop 循环执行以下3个步骤

    • 轮询监听 Accept 事件
    • 接收和处理 Accept 事件,包括和客户端建立链接并生成 NioSocketChannel,将NioSocketChannel 注册到某个 Worker NioEventLoop 的Selector 上
    • 处理 runAllTasks 的任务

      WorkerGroup 职责

      一个事件循环组,其中包含多个事件循环(NioEventLoop)
  • 每个 Worker NioEventLoop 循环执行以下3个步骤

    • 轮询监听 NioSocketChannel 上的 I/O 事件
    • 当 NioSocketChannel 有 I/O 事件触发时执行具体的 I/O 操作
    • 处理任务队列中的任务

      特性

      多路复用模型
  • 大量并发请求 —- I/O 多路复用模型 —- Selector 和 Channel

数据零拷贝

  • 传统的 Socket 服务基于 JVM 堆内存进行 Socket 读写,即 申请内存资源时,需要通过 JVM 向操作系统申请堆内存,然后 JVM 将堆内存复制一份到直接内存中,基于直接内存进行 Socket 读写。这样就存在频繁的 JVM 内存数据和 Socket 线程内存数据来回复制的问题,影响系统性能
  • Netty 的数据接收和发送均采用堆外直接内存进行 Socket 读写,堆外直接内存可以直接操作操作系统内存,不需要来回进行字节缓冲区的二次复制,大大提高了系统的性能
  • Netty 的文件传输采用 transferTo 方法。 transferTo 方法可以直接将文件缓冲区的数据基于内存映射技术发送到目标 Channel,避免了通过循环写方式导致的内存复制问题

内存重用机制

  • JVM 对于对象的分配和回收耗时很小,但 Netty 数据的接收和发送均采用堆外直接内存缓存的方式实现,而堆外直接内存缓存的分配和回收是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲重用机制

无锁化设计

  • 朵多线程会提高系统的并发度,但是线程数并不是越多越好,过多的线程会引起 CPU 的频繁切换而增加系统的负载

高性能的序列化框架

  • Netty 默认基于 Google ProtoBuf 实现数据的序列化