当前位置: 首页 > news >正文

Netty——I/O 线程模型

文章目录

  • 1. I/O 线程模型基本分类
  • 2. 传统阻塞 I/O 模型
    • 2.1 模型示意图
    • 2.2 特点
    • 2.3 问题
  • 3. Reactor 模式
    • 3.1 单 Reactor 单线程 I/O 模型
      • 3.1.1 模型示意图
      • 3.1.2 优点
      • 3.1.3 缺点
    • 3.2 单 Reactor 多线程 I/O 模型
      • 3.2.1 模型示意图
      • 3.2.2 优点
      • 3.2.3 缺点
    • 3.3 主从 Reactor 多线程 I/O 模型
      • 3.3.1 模型示意图
      • 3.3.2 优点
      • 3.3.3 缺点
  • 4. Redis 使用的 I/O 线程模型
    • 4.1 Redis 的核心模型
    • 4.2 Redis 6.0 后的优化:多线程 I/O 处理
  • 5. Netty 使用的 I/O 线程模型
    • 5.1 主 Reactor——BossGroup
    • 5.2 从 Reactor——WorkerGroup
    • 5.3 Worker 线程池(可选)
  • 6. 总结


1. I/O 线程模型基本分类

常见的 I/O 线程模型有:

  • 传统阻塞 I/O 模型
  • Reactor 模式
    • 单 Reactor 单线程 I/O 模型
    • 单 Reactor 多线程 I/O 模型
    • 主从 Reactor 多线程 I/O 模型

2. 传统阻塞 I/O 模型

2.1 模型示意图

传统阻塞 I/O 线程模型
注:紫色框代表一个线程,绿色框代表线程池,框中的内容就是这个线程需要执行的任务。

2.2 特点

  • 同步阻塞:线程发起 I/O 操作后,必须等待操作完成才能继续执行后续代码。
  • 单线程处理单连接:每个客户端连接需要分配一个独立的线程处理,线程负责完整的 I/O 操作(如读取请求、处理业务、返回响应)。

2.3 问题

  • 线程资源浪费
    • 线程数量爆炸:每个连接需要独立线程,当并发连接数达到数千甚至数万时,线程的创建、销毁和上下文切换会消耗大量 CPU 和内存资源。
    • 线程闲置:在等待 I/O 操作完成时,线程处于阻塞状态,无法执行其他任务,导致 CPU 空闲。
  • 扩展性差
    • 硬性资源限制:高并发场景下创建太多线程会触发资源耗尽,因为每个线程都需要占用一定的空间。
    • 长尾延迟:若某个连接的 I/O 操作耗时较长(如网络延迟),会导致其他连接的请求排队,整体吞吐量下降。

3. Reactor 模式

Reactor 模式的核心思想是 将 事件的 监听 与 处理 分离

3.1 单 Reactor 单线程 I/O 模型

3.1.1 模型示意图

单 Reactor 单线程 I/O 模型
说明:Reactor 监控客户端请求事件,收到事件后进行判断。如果是 Accept 事件(建立连接请求事件),则由 Acceptor 处理,然后创建一个 Handler 处理连接完成后的后续业务处理;否则由 Dispatcher 将请求事件分发给 Channel 对应的 Handler(一个 Channel 对应一个 Handler),由这个 Handler 处理事件。整个过程在 单线程 中完成。

3.1.2 优点

  • 资源消耗低:单线程处理所有 I/O 和业务,无需线程切换,内存占用少。
  • 无锁设计:天然线程安全,避免多线程竞争问题。

3.1.3 缺点

  • 性能瓶颈:所有操作均在单线程内完成,若某次操作耗时较长,会拖累整个系统响应。

3.2 单 Reactor 多线程 I/O 模型

3.2.1 模型示意图

单 Reactor 多线程 I/O 模型
说明:单 Reactor 多线程 I/O 模型单 Reactor 单线程 I/O 模型 的演进,核心改进在于 Handler 不需要再处理耗时的业务逻辑,而是将其提交给 Worker 线程池,Handler 只需要处理 I/O 事件即可。

3.2.2 优点

  • 保持 I/O 高效性:Reactor 单线程处理非阻塞 I/O,避免多线程竞争锁的问题。
  • 提升吞吐量:耗时业务由线程池并行处理,避免阻塞 Reactor 线程。

3.2.3 缺点

  • 线程安全问题:需确保业务逻辑无共享状态,或通过线程安全数据结构(如队列)传递结果。
  • 上下文切换开销:线程池规模过大会增加 CPU 调度成本。

3.3 主从 Reactor 多线程 I/O 模型

3.3.1 模型示意图

主从 Reactor 多线程 I/O 模型
说明:在这个模型中,主 Reactor(通常只有一个)负责监听并处理 Accept 事件,处理完毕后,将连接的 Channel 注册到从 Reactor(可以有多个)中,然后从 Reactor 就可以监听这个 Channel 的 I/O 事件。当从 Reactor 监听到 Channel 的 I/O 事件后,将其通过 Dispatcher 分发给 Channel 对应的 Handler,Handler 将耗时的业务逻辑处理交给 Worker 线程池进行处理。

3.3.2 优点

  • 高性能
    • 主 Reactor 仅处理连接建立,避免高并发下的 Accept 瓶颈。
    • 从 Reactor 多线程并行处理 I/O,充分利用多核 CPU。
  • 弹性扩展:从 Reactor 数量和线程池规模可按需调整。
  • 资源隔离:从 Reactor 之间无共享状态,避免锁竞争。

3.3.3 缺点

  • 复杂度极高:需管理主从 Reactor 协作、负载均衡、线程间通信。
  • 上下文切换开销:从 Reactor 线程过多可能导致 CPU 调度开销。

4. Redis 使用的 I/O 线程模型

4.1 Redis 的核心模型

Redis 的核心采用 单 Reactor 单线程模型

  • Reactor 角色:主线程作为唯一的 Reactor,负责监听所有客户端连接的事件(如连接建立、读写请求)。
  • 线程模型:所有 网络请求 和 命令处理 均在主线程中串行执行,避免了线程切换开销。

这种设计适合 Redis 的内存数据库特性,因为命令执行速度极快(内存操作),单线程不会成为瓶颈。

4.2 Redis 6.0 后的优化:多线程 I/O 处理

对于 Redis 来说,内存操作速度极快,性能瓶颈只能是 网络 I/O 了。Redis 从 6.0 版本开始引入多线程处理网络 I/O,但多线程仅用于网络 I/O,命令处理仍在主线程中串行执行

  • I/O 线程池:通过配置 io-threads 参数,主线程将网络 I/O 任务(如读取请求、发送响应)分配给多个子线程并行处理。
  • Reactor 模型变化:主线程仍负责事件分发,但部分 I/O 操作由多线程分担,属于 单 Reactor 多线程模型 的变种。

5. Netty 使用的 I/O 线程模型

Netty 使用的 I/O 线程模型是 主从 Reactor 多线程 I/O 模型,角色划分如下:

5.1 主 Reactor——BossGroup

  • 职责:
    • 接收客户端连接:监听服务器端口,处理 OP_ACCEPT 事件(连接建立事件)。
    • 分配连接到 Worker Group:将新建立的连接注册到 WorkerGroup 中的某个线程。
  • 实现类:NioEventLoopGroup
  • 线程数:通过构造函数 NioEventLoopGroup(1) 显式设置,线程数为 1

注意:通常根据服务器性能调整,但无需过多线程,因为连接建立是轻量级操作。

5.2 从 Reactor——WorkerGroup

  • 职责:
    • 处理已连接通道的读写事件:监听 OP_READ/OP_WRITE 事件,完成数据的读取和发送。
    • 分发事件到 ChannelHandler:将读取的数据传递给用户自定义的 ChannelPipeline 中的处理器链。
  • 实现类:NioEventLoopGroup
  • 线程数:对于 I/O 密集型任务,为了充分利用多核 CPU,通常将线程数设置为 Runtime.getRuntime().availableProcessors() * 2,这个也是 NioEventLoopGroup 的空参构造器的默认线程数。

注意:虽然对于 I/O 密集型任务可以将线程数设置为 CPU 核数的 2 倍,但实际上为了发挥服务器的最大性能,仍 需要通过压力测试来得到性能最优的线程数

5.3 Worker 线程池(可选)

  • 职责:处理阻塞业务逻辑,若业务逻辑涉及数据库查询、文件读写等阻塞操作,将任务提交到线程池,避免阻塞 WorkerGroup 中的线程。
  • 实现类:通常使用 DefaultEventExecutorGroupThreadPoolExecutor

注意:只有在 ChannelInitialzer 初始化通道时给 ChannelHandler 设置了别的线程池,ChannelHandler 处理任务时才会使用其他线程(如下代码所示),否则 ChannelHandler 使用的是 WorkerGourp 的线程。如果 ChannelHandler 处理任务需要花费大量的时间,可能会导致 WorkerGroup 的线程无法处理其他 I/O 事件。所以 对于耗时较长的 ChannelHandler,在初始化通道时需要给它设置线程池

@Override
protected void initChannel(SocketChannel ch) throws Exception {
	// 给 ChannelHandler 的实现类 MyServerHandler 设置线程池
    ch.pipeline().addLast(new DefaultEventExecutorGroup(10), new MyServerHandler());
}

6. 总结

早期的 I/O 线程模型是 传统阻塞 I/O 模型,每个客户端的连接对应一个线程,这个线程需要监听并处理这个客户端的 I/O 事件。在客户端很多的情况下,大量申请线程资源可能会导致内存溢出 OOM。

之后出现了 Reactor 模式,核心思想是 分离事件的监听与处理,使用一个线程监听多个客户端连接的 I/O 事件,监听到之后将其分发到对应的 Handler,让这个 Handler 进行处理。

Reactor 模式 最简单的实现方式是 单 Reactor 单线程模型,它的缺点是 Reactor 线程还需要处理业务逻辑,可能导致其他客户端连接的 I/O 事件无法被及时处理。

单 Reactor 单线程模型 进行优化,于是就得到了 单 Reactor 多线程模型,这种模型外置了一个 Worker 线程池,用于处理业务逻辑。当 Worker 线程处理完后,Handler 再将数据返回。

随着互联网民的增多,单 Reactor 多线程模型 已经无法处理超多客户端连接的场景,此时就需要使用 主从 Reactor 多线程模型。主 Reactor 负责监听并处理客户端的连接事件,一旦连接成功,就将这个连接注册到某个从 Reactor 上。多个从 Reactor 负责监听客户端的 I/O 事件,如果有 I/O 事件,则会将其分发给对应的 Handler,Handler 只需要处理 I/O 事件。至于耗时很长的业务逻辑,可以委托给 Worker 线程池。

Redis 是一个高性能的缓存数据库,它使用的是 单 Reactor 单线程模型。由于内存操作是极快的,网络 I/O 成为了性能的瓶颈,所以 Redis 6.0 引入了多线程来处理网络 I/O 事件,核心的命令处理仍只能被主线程串行执行。

Netty 是 Java 的一个高性能网络框架,它采用了 主从 Reactor 模型,有着 BossGroup 处理连接事件,WorkerGroup 处理 I/O 事件的设计,如果有耗时较长的业务逻辑,可以交给额外的线程池处理。

相关文章:

  • 最长连续子序列和的所含元素 -- Kadane算法拓展
  • 【C++网络编程】第8篇:协议设计与序列化(Protobuf、FlatBuffers)
  • 流式ETL配置指南:从MySQL到Elasticsearch的实时数据同步
  • 【设计模式】工厂模式
  • 信息学奥赛一本通 1514:【例 2】最大半连通子图 | 洛谷 P2272 [ZJOI2007] 最大半连通子图
  • vue watch数据监听
  • R语言——字符串
  • RTSP/Onvif安防监控平台EasyNVR抓包命令tcpdump使用不了,该如何解决?
  • 模型搭建与复现
  • 【Linux网络-多路转接select】
  • Active Directory (AD): 企业网络用户管理的重要性及 AD 迁移方法
  • UNIX网络编程笔记:TCP、UDP、SCTP编程的区别
  • 解决 MySQL 的 sql_mode 中包含 only_full_group_by模式导致group by SQL报错
  • PHP eval 长度限制绕过与 Webshell 获取
  • 穿透Session 0隔离
  • 【每日算法】Day 6-1:哈希表从入门到实战——高频算法题(C++实现)
  • 网络安全基础:五类安全服务、八种安全机制与OSI七层模型的全面解析
  • HTML——什么是块级元素,什么是内联元素,有何区别
  • 使用Django创建项目及介绍
  • OBS虚拟背景深度解析:无需绿幕也能打造专业教学视频(附插件对比)
  • 怎样做网站海报/网站seo关键词设置
  • 房山网站开发/百度做广告多少钱一天
  • 网站谁做的比较好看的/seo做得比较好的公司
  • 试用型网站/山东seo多少钱
  • 《高性能网站建设指南》/开发一个网站需要多少钱
  • 环保网站可以做哪些内容/企业网站推广效果指标分析