Netty知识点

一、Netty的零拷贝机制

零拷贝的基本理念:避免在用户态和内核态之间拷贝数据,从而降低 CPU 占用和内存带宽的消耗除了系统层面的零拷贝。

1、FileRegion 接口
FileRegion 是 Netty 提供的用于文件传输的接口,它通过调用操作系统的 sendfile 函数实现文件的零拷贝传输。sendfile 函数可以将文件数据直接从文件系统发送到网络接口,而无需经过用户态内存拷贝。
在这里插入图片描述
2、ByteBuf
ByteBuf提供了直接缓冲区(Direct Buffer)和堆缓冲区(Heap Buffer)两种类型

  • HeapBuffer: 网络 IO、文件 IO 这种系统调用时,需要从 JVM 堆内存拷贝一次到内核态(socket buffer 或page cache); 即:JVM堆内 → JVM堆外(临时内存) → 内核内存
  • DirectBuffer: JVM 分配的堆外内存可以直接暴露给内核,系统调用时 省了一次 JVM 内存 →内核的拷贝。即:JVM堆外(直接可用) → 内核内存

DirectBuffer 的好处是“避免 JVM 内存到内核内存的拷贝”

3、CompositeByteBuf
它是 Netty 提供的一种组合缓冲区,它可以将多个 ByteBuff 实例组合成一个逻辑上的缓冲区,而不需要实际拷贝数据。这种方式可以避免内存拷贝,提高内存使用效率。
没有 CompositeByteBuf 的话,我要合并三段数据(无论堆内还是 DirectBuffer)都得 拷贝 3 次字节;
有了 CompositeByteBuf,就可以 0 拷贝地把三段数据拼成一个整体,只在 最终 write 到 socket 时拷贝一次!

4、内存映射文件(Memory-Mapped File)
Netty 支持使用内存映射文件来实现文件的零拷贝。通过 MappedByteBuffer,文件可以被映射到内存中,并直接进行读取和写入操作,而不需要额外的内存拷贝。

MappedByteBuffer 是一种“把文件变成内存”的操作方式,它不是真正读了文件,而是借助操作系统提供的“内核页缓存 + 页面调度机制”来让你用内存方式读写文件内容,从而跳过传统 IO 调用,提升性能,减少拷贝,即减少了传统 read() IO 中“从内核态拷贝到用户态”的那一步内存拷贝。

传统文件读取流程:read() 的拷贝路径
磁盘 →(DMA)→ 内核页缓存(Page Cache) → memcpy → 用户空间 byte[]
1️、磁盘读取文件数据,用 DMA 拷贝到 Page Cache(内核态)
2️、系统调用 read() 把 Page Cache 中的数据 memcpy 一份给用户传进来的 byte[](用户态)

这一步会发生一次额外的数据拷贝:内核态 → 用户态

MappedByteBuffer 是怎么优化的?

磁盘 →(DMA)→ 内核页缓存(Page Cache)→ 直接映射到你的虚拟地址空间

1、把 Page Cache 的那块物理内存直接映射到你的进程的虚拟地址空间中
2、你访问这块内存时,系统不会再复制数据,而是让你直接读取那块 Page Cache 中的物理页

所以,数据是:
• 从磁盘 → Page Cache(依然是必要的!)
• 正省掉的是 “Page Cache → JVM堆内存”这一步;

FileRegion和ByteBuf功能上的区别

FileRegion 是为了解决 “文件发送”时避免拷贝(sendfile);
ByteBuf 是为了处理 内存中动态字节数据的读写、拼接、编码、解析。

两者职责完全不同,不是你用上 FileRegion,ByteBuf 就没用了,而是:
• 要发的是文件,就用 FileRegion
• 要发的是内存里的消息、请求体、序列化数据、协议帧,那就必须用 ByteBuf

功能FileRegionByteBufMappedByteBuffer
定位用途**高效“文件→网络”**发送内存中动态字节操作直接把文件映射到内存操作
适用场景大文件下载、文件转发普通的内存字节处理(协议解析、缓存)大文件读写、随机访问、内存级文件解析
是否省拷贝✅ sendfile 内核实现,零拷贝,省 内核态→用户态❌ IO 时还是要经历 内核态 → 用户态✅ mmap 机制,省 内核态 → 用户态 拷贝
是否跨内核和用户态直接文件 → socket,用户态不介入用户态玩数据,涉及 IO 时交互频繁虽然你用的是用户态地址,但指向的是 Page Cache
写操作友好性❌ 不可修改,只是数据“搬运工”✅ 任意读写内存⚠️ 写入时会 脏页同步到 Page Cache → 文件,有刷盘成本
核心优势零拷贝、文件转发超快灵活读写、内存拼接、操作方便直接访问文件内容、适合大文件访问就像数组一样快
底层依赖sendfile() 系统调用JVM堆或 DirectBuffermmap() 系统调用

二、 Netty如何解决粘包拆包问题?

Netty 提供了丰富的自带解码器为我们解决粘包和拆包的问题,也可以让我们自定义序列化解码器。

Netty 自带的解码器
1、DelimiterBasedFrameDecoder:分隔符解码器,使用特定分隔符来分割消息。
2、FixedLengthFrameDecoder:固定长度的解码器,可以按照指定长度对消息进行拆包,如果长度不够的话,可以使用空格进行补全,适用于每个消息长度固定的场景。
3、LengthFieldBasedFrameDecoder:可以根据接收到的消息的长度实现消息的动态切分解码,适用于消息头包含表示消息长度的字段的场景。
4、LineBasedFrameDecoder:发送端发送数据包的时候,数据包之间使用换行符进行分割,LineBasedFrameDecoder就是直接遍历 ByteBuf中的可读字节,根据换行符进行数据分割。

三、 介绍一下Reactor线程模型

Reactor 是服务端在网络编程时的一个编程模式,主要由一个基于 Selector (底层是select/poll/epoll)的死循环线程,也称为 Reactor 线程

基于事件驱动,将 I/0 操作抽象成不同的事件,每个事件都配置对应的回调函数,由Selector 监听连接上事件的发生,再进行分发调用相应的回调函数进行事件的处理。

Reactor 线程模型分为三种,分别为单 Reactor 单线程模型、单 Reactor 多线程模型、主从 Reactor 多线程模型。

1)单 Reactor 单线程模型:所有的操作都是由一个 I/O 线程处理。
·优点:对系统资源消耗较少
·缺点:没办法支持高并发场景。

2)单 Reactor 多线程模型:一个线程负责接收建连事件和后续的连接 I/0 处理(read
send等),线程池处理具体业务逻辑。
·优点:可以很好地应对大部分的场景,也提高了事件的处理效率。
·缺点:并发量大的时候,一个线程可能没办法接收所有的事件请求,可能导致性能瓶颈。

3)主从 Reactor 多线程模型:主 Reactor 线程负责接收建连事件,从 Reactor 线程负责处理建连后的后续连接 I/0 处理(read、send等),线程池处理具体业务逻辑
·优点:解决海量并发请求。
·缺点:相对而言,实现复杂度较高,对系统资源的管理要求会高一点

Reactor 模式的核心意义
Reactor 的核心在于“对事件作出反应”。用一个线程(或少量线程)来监听多个连接上的事件,根据事件类型分发调用相应的处理逻辑,从而避免为每个连接都分配一个线程。与传统模型对比,传统阻塞 I/0 是一个线程对应一个连接,资源浪费严重;而 Reactor 模式能实现一对多的映射,更适合高并发场景。

四、 有几种I/O模型?

1)同步阻塞I/O(Blocking I/O,BIO)
线程调用 read 时,如果数据还未到来,线程会一直阻塞等待;数据从网卡到内核,再从内核拷贝到用户空间,这两个拷贝过程都为阻塞操作。
·优点:实现简单,逻辑直观;调用后直接等待数据就绪
·缺点:每个连接都需要一个线程,即使没有数据到达,线程也会被占用,导致资源浪费,不适合高并发场景。

2)同步非阻塞I/0(Non-blocking I/O,NIO)在非阻塞模式下,read 调用如果没有数据就绪会立即返回错误(或特定状态),不会阻塞线程;应用程序需要不断轮询判断数据是否就绪,但当数据拷贝到用户空间时依然是阻塞的。
·优点:线程不会长时间阻塞,可以在无数据时执行其他任务;适用于部分实时性要求较高的场景。
·缺点:轮询方式会频繁进行系统调用,上下文切换开销较大,CPU占用率较高,不适合大规模连接。

3)I/0 多路复用
通过一个线程(或少量线程)使用 select、等系统调用,监控多个连接的poll ,epoll状态;只有当某个连接的数据就绪时,系统才通知应用程序,再由应用程序调用read 进行数据读取(读取时仍为阻塞操作)。
·优点:大大减少了线程数量和上下文切换,能高效处理大量并发连接;资源利用率高。
·缺点:依赖系统内核的支持,不同的多路复用实现(如 select vs epoll)有各自局限。

4)信号驱动 I/O
由内核在数据就绪时发出信号通知应用程序,应用程序收到信号后再调用read(依然阻塞)
·优点:理论上可以避免轮询,数据就绪时由内核主动通知。
·缺点:对于 TCP 协议,由于同一个信号可能对应多种事件,难以精确区分(所以实际应用中使用较少)。

五、 Netty应用场景

Netty 的应用场景主要有以下几个:许多框架底层通信的实现,比如说 RocketMQ、Dubbo、Elasticsearch、Cassandra等,底层都使用到了 Netty。

游戏行业,在游戏服务器开发中,Netty 用于处理大量并发的游戏客户端连接,提供低延迟的网络通信能力

实现一个通讯系统,比如聊天室、IM 等,处理高并发的实时消息传输。

物联网即 IOT 场景,Netty 可用于设备与服务器之间的通信,处理设备数据的收集和命令下发。

六、 为什么不使用原生的NIO而选择Netty?

使用 Netty 的优势:
1)Netty 封装了 NIO 的复杂 API,提供了更简单、直观的编程接口,使开发者更容易上手和维护。
2)Netty 提供了优化的多线程模型(如 Reactor 模型),可以更高效地处理 //O 事件和任务调度,提升并发处理能力。
3)Netty 支持多种传输协议(http、dns、tcp、udp 等等),并且有自带编码器,解决了 TCP 粘包和拆包的问题。
4)在原生 NIO 的基础上解决了 Selector 空轮询 Bug 的问题,且准备内部的细节做了优化,例如 JDK 实现的 selectedKeys 是 Set 类型,Netty 使用了数组来替换这个类型,相比 Set 类型而言,数组的遍历更加高效,其次数组尾部添加的效率也高于 Set,毕竟 Set还可能会有 Hash 冲突,这是 Netty 为追求底层极致优化所做的。
5)采用了零拷贝机制,避免不必要的拷贝,提升了性能。

七、 Netty性能高的原因

Netty 性能高的主要原因如下:

非阻塞 I/O 模型(如何监听事件):Netty 底层使用了 NIO 非阻塞模型,并且利用IO多路复用,通过 Selector 监听多个 Channel的 IO 事件,使得系统资源得到了充分利用,减少了线程开销。

优秀的线程模型(事件来了由谁处理):Netty 底层有很多优秀的线程模型,比如 Reactor 模型、主从Reactor 模型、多线程模型等,可以高效地发挥系统资源的优势,减少锁冲突,实现无锁串行,针对不同业务场景的诉求,可以自定灵活控制线程,提高系统的并发处理能力。

零拷贝(数据传输):DirectBuffer 减少堆内外的拷贝、CompositeBuffer 减少数据拼接时的拷贝、FileRegion 减少文件传输时的拷贝。

高效的内存操作与内存池设计:ByteBuf提供了丰富的功能,如动态扩展、复合缓冲区等,能高效地进行内存操作,并使用内存池技术来优化 ByteBuf的分配和回收,减少频繁的内存分配和释放操作,提高性能。

八、 Netty如何解决NIO中的空轮询BUG?

Netty 实际上并没有解决 JDK 原生 NIO 中空轮询 bug,而是通过其他途径绕开了这个错误。

具体操作如下:
1、统计空轮询次数:Netty 通过 selectCnt 计数器来统计连续空轮询的次数。每次执行Selector.select()方法后,如果发现没有 I/O 事件,selectCnt 就会递增,

2、设置阈值:Netty 定义了一个阈值 SELECTOR AUTO REBUILD THRESHOLD,默认值为 512。当空轮询次数达到这个值时,Netty 会触发重建 Selector 的操作。

3、重建 Selector:当达到空轮询的阈值时,Netty 会创建一个新的 Selector,并将所有注册的 Channel 从旧的 Selector 转移到新的 Selector 上。这一过程涉及到取消旧日 Selector上的注册,并在新 Selector 上重新注册 Channel。

4、关闭旧的 Selector:在成功重建 Selector 并将Channel 重新注册后,Netty 会关闭旧的 Selector,从而避免继续在旧的 Selector 上发生空轮询。总结来看,就是通过 selectCnt 统计没有 I/0 事件的次数来判断当前是否发生了空轮询如果发生了就重建一个 Selector 替换之前出问题的 Selector,所以说 Netty 实际上没解决空轮询的 bug,只是绕开了这个问题。

空轮询 bug 原因:
当连接的 Socket 被突然中断(如对端异常关闭)时,epoll 会将该 Socket 的事件标记为EPOLLHUP 或 EPOLLERR,导致 Selector 被唤醒。然而,SelectionKey 并未定义处理这些异常事件的类型,导致 Selector 被唤醒后,无法处理这些异常事件,从而进入空轮询状态,导致 CPU 占用率过高。

Selector 底层其实用的是 epoll(Linux):
• epoll_wait() 支持监听几万个 fd(也就是 SocketChannel)
• 这些 fd 上注册的事件一般是:
• EPOLLIN → 对应 Java 的 OP_READ
• EPOLLOUT → 对应 Java 的 OP_WRITE
• 还有 EPOLLHUP(挂起) / EPOLLERR(错误)这两种“系统级”异常事件

⚠ 问题点:异常事件虽然会唤醒 epoll,但 Java 层不处理,Selector 会调用 native epoll_wait,被 EPOLLHUP、EPOLLERR 唤醒没问题。但是,Java 的 SelectionKey 只支持处理 OP_READ / OP_WRITE / OP_ACCEPT / OP_CONNECT。它没有暴露“这个 key 是因为异常唤醒的”这种能力,所以唤醒后,我们调用 selectedKeys(),结果是空的!这就等于:“你闹钟响了我就醒了,但你根本没安排任务,那我只能干站着,反复被吵醒”

🚨 所以空轮询本质就是:
Selector 被异常唤醒,但 Java 应用层没有能力识别这个异常事件,也没法处理它,导致不断 select → 空 → select → 空,形成死循环 + 高 CPU。

为啥重建 Selector 就能好?
大多数空轮询 bug 触发的根因是:
• 某些 fd(Socket)状态不干净
• 或某个 selector 对象在 native epoll 里状态乱了
所以重建 selector + 重新注册 Channel的过程,相当于:
✨重新 clean 一遍所有状态,把原来 epoll 里的鬼影都清除掉了。

🛠 重建 Selector 的关键步骤:

  1. 创建一个新的 Selector
  2. 遍历旧 Selector 中的所有 SelectionKey
  3. 对每个 key:
    o 取出 channel 和监听事件(interestOps)
    o 尝试用 channel.register(newSelector, interestOps, attachment) 重新注册
    o 如果注册成功 → 加入新 Selector
    o 如果注册失败(一般就是异常 socket) →
    ✅ 从旧的 Selector 中 cancel
    ✅ 手动关闭 Channel
    ✅ 打印警告日志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92036.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka的基本使用

目录 认识Kafka 消息队列 消息队列的核心概念 核心价值与解决的问题 Kafka ZooKeeper Kafka的基本使用 环境安装 启动zookeeper 启动Kafka 消息主题 创建主题 查询主题 修改主题 发送数据 命令行操作 JavaAPI操作 消费数据 命令行操作 JavaAPI操作 认识Kafka…

Flink2.0学习笔记:Table API SQL

stevensu1/EC0720 表 API 和 SQL# 表 API 和 SQL——用于统一流和批处理 加工。表 API 是适用于 Java、Scala 和 Python 的语言集成查询 API,它 允许组合来自关系运算符的查询,例如 selection、filter 和 join in 一种非常直观的方式。Flink 的 SQL 支…

【 SpringAI核心特性 | Prompt工程 】

1. Prompt 工程 基本概念:Prompt ؜工程又叫提示‏词工程,简单来说,就是输入‌给 AI 的指令。 比如下面‏这段内容,就是提示词: 请问桂林电子科技大学是一个怎么样的学校?1.1 Prompt分类 在 AI ؜对话中…

windows wsl2-06-docker hello world

hello-world 例子 就像其他任何一门语言一样,我们来体验 docker 的 hello world $ docker run hello-world但是报错 :~$ docker run hello-world Unable to find image hello-world:latest locally docker: Error response from daemon: Get "https://registry…

Python知识点4-嵌套循环break和continue使用死循环

一、循环【重点掌握】 1.嵌套循环类似于嵌套if语句 语法&#xff1a; while 表达式1&#xff1a;while 表达式2&#xff1a;语句# 1. # 循环5次&#xff0c;打印0~4 m 0 while m < 5:print(m)m 1 # 循环3次&#xff0c;打印0~2 n 0 while n < 3:print(n)n 1print(&qu…

将HTML+JS+CSS数独游戏包装为安卓App

HTMLJSCSS制作一个数独游戏-CSDN博客 中开发了一个数独游戏&#xff0c;这个数独游戏提供了一次性回退到指定步骤的辅助功能&#xff0c;在解决复杂数独问题时十分有帮助&#xff0c;可作为玩数独游戏的辅助工具&#xff0c;因此&#xff0c;考虑将它改装成安卓App安装在手机上…

编程语言Java入门——核心技术篇(一)封装、继承和多态

同专栏基础知识篇写在这里&#xff0c;有兴趣的可以去看看&#xff1a; 编程语言Java入门——基础知识篇&#xff08;一&#xff09;-CSDN博客 编程语言Java入门——基础知识篇&#xff08;二&#xff09;-CSDN博客 编程语言Java入门——基础知识篇&#xff08;三&#xff0…

【39】MFC入门到精通——C++ /MFC操作文件行(读取,删除,修改指定行)

文章目录1 通过关键词&#xff0c;读取某一行 &#xff08;3种方法&#xff09;2 删除 指定行3 修改 指定行1 通过关键词&#xff0c;读取某一行 &#xff08;3种方法&#xff09; 通过定位关键词&#xff0c;读取某一行信息,返回CString //通过定位关键词&#xff0c;读取某…

5 种可行的方法:如何将 Redmi 联系人备份到 Mac

将 Redmi 联系人备份到 Mac 是防止因手机损坏、丢失或更换设备而导致数据丢失的重要措施。虽然云服务提供了便利性&#xff0c;但拥有离线备份可以提供额外的安全性&#xff0c;而无需完全依赖互联网。如果您想知道如何将 Redmi 联系人备份到 Mac&#xff0c;本文将为您介绍 5 …

LeRobot 具身智能机械臂 SO-ARM100 从搭建到训练全流程

今天给大家分享一下 LeRobot 具身智能机械臂 SO-ARM100 的完整使用流程&#xff0c;包括设备组装、环境配置、远程控制、数据录制到模型训练的全过程。适合刚入门具身智能的小伙伴参考学习。 一、前期准备与资源获取 在开始之前&#xff0c;我们需要准备好相关的资源和工具&a…

LINUX720 SWAP扩容;新增逻辑卷;逻辑卷扩容;数据库迁移;gdisk

SWAP空间扩展 方法一 增加硬盘或分区扩展 swap -s mkswap /dev/sdd6 blkid /dev/sdd6 swapon /dev/sdd6 swapon -s vim /etc/fstab /dev/sdd6 swap swap defaults 0 0 开机自动扩容 swap -s [rootweb ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sd…

Python 进程间通信:TCP安全加密数据传输

最近在写安全方面的程序&#xff0c;有需求&#xff0c;就做了这些TCP加密数据传输类。 utils.safeUtils的内容详见&#xff1a; SafeObj&#xff1a;Python 高安全性加密数据容器类-CSDN博客SafeKey&#xff1a;Python 高安全性加密密码容器类-CSDN博客 如有任何问题或漏洞欢迎…

Windows批量修改文件属性方法

标题使用icacls命令&#xff08;推荐批量操作&#xff09;打开管理员权限的命令提示符&#xff08;CMD&#xff09;执行以下命令&#xff1a;cmd icacls "文件夹路径" /grant 用户名:(OI)(CI)F /T /C 参数说明&#xff1a;(OI)&#xff1a;对象继承 - 适用于文件夹(C…

Entity Component System架构

ECS架构 1 简介 在当今快速发展的软件开发领域&#xff0c;游戏开发、实时模拟等场景对系统的性能、灵活性和可扩展性提出了极高的要求。传统的面向对象架构在面对复杂且动态变化的实体时&#xff0c;往往会出现代码耦合度高、扩展性差等问题。​ ECS&#xff08;Entity - Com…

.vscode 扩展配置

一、vue快捷键配置 在项目.vscode下新建vue3.0.code-snippets 每当输入vue3.0后自动生成代码片段 {"Vue3.0快速生成模板": {"scope": "vue","prefix": "Vue3.0","body": ["<template>"," &…

一个基于阿里云的C端Java服务的整体项目架构

1.背景介绍 总结一下工作使用到的基于通常的公有云的项目整体架构&#xff0c;如何基于公有云建设安全可靠的服务&#xff0c;以阿里云为例的整体架构&#xff1b;1. 全局流量治理层&#xff08;用户请求入口&#xff09;1.1 域名与 DNS 解析域名注册与备案&#xff1a;通过阿里…

《剥开洋葱看中间件:Node.js请求处理效率与错误控制的深层逻辑》

在Node.js的运行时环境中&#xff0c;中间件如同一系列精密咬合的齿轮&#xff0c;驱动着请求从进入到响应的完整旅程&#xff0c;而洋葱模型则是这组齿轮的传动系统。它以一种看似矛盾的方式融合了顺序与逆序、分离与协作——让每个处理环节既能独立工作&#xff0c;又能感知全…

GaussDB union 的用法

1 union 的作用union 运算符用于组合两个或更多 select 语句的结果集。2 union 使用前提union 中的每个 select 语句必须具有相同的列数这些列也必须具有相似的数据类型每个 select 语句中的列也必须以相同的顺序排列3 union 语法select column_name(s) from table1 union sele…

构建足球实时比分APP:REST API与WebSocket接入方案详解

在开发足球实时比分应用时&#xff0c;数据接入方式的选择直接影响用户体验和系统性能。本文将客观分析REST API和WebSocket两种主流接入方案的技术特点、适用场景和实现策略&#xff0c;帮助开发者做出合理选择。一、REST API&#xff1a;灵活的数据获取方案核心优势标准化接口…

Linux文件系统三要素:块划分、分区管理与inode结构解析

理解文件系统 我们知道文件可以分为磁盘文件和内存文件&#xff0c;内存文件前面我们已经谈过了&#xff0c;下面我们来谈谈磁盘文件。 目录 一、引入"块"概念 解析 stat demo.c 命令输出 基本信息 设备信息 索引节点信息 权限信息 时间戳 二、引入"分区…