Netty源码分析二NioEventLoop 剖析

剖析方向

NioEventLoop是一个重量级的类,其中涉及到的方法都有很复杂的继承关系,调用链,要想把源码全部过一遍工作量实在是太大了,于是小编就基于下面的这些常见的问题来对NioEventLoop的源码来进行剖析

1.Seletor何时创建

    1.1Selector为什么有两个Selector成员

2.nio线程在何时启动

3.每次循环时什么时候会进入SelecStrategy.SELECT分支

   3.1何时会select阻塞,会阻塞多久

4.nio空轮询bug在哪里体现,Netty如何解决的?

5.ioRatio控制什么,设置为100有什么作用

6.Netty中对selectKeys优化是怎么回事

我们需要时刻记住下面这两点:

NioEventLoop的重要组成:Selector、线程、任务队列

NioEventLoop 线程不仅要处理 IO 事件,还要处理 Task(包括普通任务和定时任务)

具体剖析NioEventLoop

NioEventLoop的重要组成

首先我们先来看一下NioEventLoop的几个重要的成员变量

Selector:

线程:0

线程不在NioEventLoop本类里面,在其祖父类SingleThreadEventExecutor里面

下面的Executor就是线程的执行器

任务队列:

在其曾祖父类AbstractScheduledEventExecutor里面有处理定时任务的任务队列

Selector何时创建

快捷键Ctrl+F12可以用来查看当前类的方法和成员变量,我们找Selector的构造方法

我们来研究一下上面标注的这行代码:

先来看一下SelectorTuple是什么:

我们可以看到SelectorTuple是一个内部类,里面封装了Selector

然后再去看一下真正创建Selector的方法openSelector()

我们发现与NIO中的Selector创建一样也是通过SelectorProvider这个类创建的,SelectorProvider是一个抽象工厂类,Selector的创建过程体现了工厂设计模式

那么看完源码之后就可以回答上面的问题了:Selector是何时创建的呢?在构造方法调用的时候创建。

Selector为什么有两个Selector成员

我们读源码可以看到在NioEventLoop这个类中有两个Selector成员

我们来看openSelector()这个方法

我们发现调用工厂类获得的Selector实例赋值给了unwrappedSelector,此处的Selector实例是与NIO中的Selector实例是同一种,因为它们都是通过同一个工厂类获得的,因此unwrappedSelector才是真正的底层NIO的Selector。这里讲一下Netty中的Selector与NIO中的Selector区别:NIO中的Selector内部有一个selectedKeys集合,这个集合里面存储了监听的事件类型SelectionKey

我们可以看到这个集合是一个Set集合,但是Set由于底层是一个哈希表,哈希表的遍历需要遍历每一个哈希桶,因此遍历的性能不高。Netty中的selectedKeys集合就对这点做了优化,改用数组来存储SelectionKey提高了遍历性能。我们可以看是通过反射机制来改用数组进行存储的。

看完源码我们可以回答上面的问题了:unwrappedSelector是原生的NIO中的Selector,selector是Netty中经过优化后的Selector,原生的Selector采用Set存储SelectionKey,NIO中的Selector采用数组存储SelectionKey,为了在遍历SelectionKey时提高性能,同时在其他地方使用到原生的Selector,因此有两个Selector成员。

nio线程在何时启动

为了了解清除nio线程是如何启动的,我写了一个测试类,以debug的模式来分析。

进入到execute方法中

我们看到先是做了一个非空判断,然后调用inEventLoop()方法。我们去看一下inEventLoop()的源码。

我们可以看到就是用于判断当前线程是否为EventLoop线程,刚开始EventLoop中的线程为空,所以肯定返回false。

之后把任务加入到任务队列里面,之后进入到一个if判断中进行首次调用,启动线程。

进入到startThread()方法,这里修改了state状态后进入方法,确保EventLoop线程开启只被调用一次。

进入到doStartThread();这里面就是真正地开启EventLoop线程

thread = Thread.currentThread(),在executor执行器中创建了线程,并把当前线程赋值给了EventLoop的thread成员变量中,此时一个nio线程就初始化成功了。

进入到SingleThreadEventExecutor.this.run();

我们可以看到这是一个死循环,这个死循环里不断地寻找IO事件以及是否有要处理地Task任务。

看完源码之后我们就可以回答上面的问题了

当首次调用execute方法时,会启动EventLoop的Nio线程,通过一个state状态位来控制线程只会启动一次。

每次循环时什么时候会进入SelecStrategy.SELECT分支

源码位置

决定是否进入分支的代码如下:

选中calculateStrategy方法,由于是一个接口,因此使用快捷键ctrl+alt+b进入到这个接口的实现类中

看到这里可以知道当hasTasks变量为false时(没有任务要执行时才会进入SelecStrategy.SELECT分支);当有任务时会调用selectNow方法顺便拿到io事件,连同普通任务一起交给下面的逻辑去执行。

何时会select阻塞,阻塞多久?

当进入到SelectStrategy.SELECT分支后,我们发现不会一直阻塞,有一个阻塞时间curDeadlineNanos。

以下是Netty中NioEventLoop类的select方法的一部分源码,用于说明阻塞时间的计算:

private int select(long deadlineNanos) throws IOException {if (deadlineNanos == NONE) {// 无定时任务,直接阻塞return selector.select();}// 计算阻塞时间long timeoutMillis = deadlineNanos - System.nanoTime();if (timeoutMillis <= 0) {return selector.selectNow();}// 阻塞指定的毫秒数return selector.select(timeoutMillis);
}

在这个方法中,deadlineNanos表示下一次定时任务的到期时间(以纳秒为单位)。如果deadlineNanosNONE,表示没有定时任务,select方法会无限期地阻塞,直到至少有一个通道的I/O事件就绪。如果deadlineNanos是一个具体的值,Netty会计算当前时间和deadlineNanos之间的差值,得到阻塞时间timeoutMillis

如果timeoutMillis小于或等于0,表示定时任务已经到期,selectNow()方法会被调用,这个方法不会阻塞,立即返回就绪的通道。如果timeoutMillis大于0,select方法会阻塞最多timeoutMillis毫秒,直到有I/O事件就绪或者阻塞时间超过timeoutMillis

这个设计确保了select方法的阻塞时间是根据下一次定时任务的到期时间来动态调整的,这样可以在保证I/O事件得到及时处理的同时,也能按时执行定时任务。

nio空轮询bug在哪里体现,Netty如何解决的?

NIO(New I/O)的“空轮询”Bug是指在某些操作系统和JDK版本中,Selector可能会错误地唤醒,即使没有实际的I/O事件发生,这会导致CPU 100%的问题。这个问题通常发生在Linux系统上,尤其是在Epoll模式下,且在使用old-style (poll(2)) epoll events的Linux内核版本中。

在Netty中,这个问题体现为EventLoop线程会不断地被唤醒,即使没有新的I/O事件需要处理,从而导致不必要的CPU消耗。

Netty解决这个问题的方法是使用一个称为“时间轮询检测”的机制。Netty会记录连续的空轮询次数,当空轮询次数达到一个阈值时,它会重建Selector,这样就可以避免这个问题。重建Selector意味着创建一个新的Selector实例,并将所有的通道注册到新的Selector上。

以下是Netty中处理空轮询的简化代码片段:

int selectCnt = 0;
for (;;) {selectCnt++;int selectedKeys = selector.select(timeoutMillis);if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks()) {// 处理就绪的I/O事件}if (selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {// 重建Selectorselector = selectRebuildSelector(selector);selectCnt = 0;}
}

ioRatio控制什么,设置为100有什么作用

ioRatio是一个用于控制I/O操作和非I/O任务执行时间的比例的参数。这个参数是在EventLoop中设置的,用于调整在事件循环中处理I/O事件和执行非I/O任务的时间比例。

ioRatio的值是一个百分比,表示在事件循环的一次迭代中,用于处理I/O事件的最大时间比例。例如,如果ioRatio设置为50,那么在每次事件循环迭代中,Netty会尽量保证至少50%的时间用于处理I/O事件,而剩余的50%的时间用于执行非I/O任务。

阅读上面的源码我们可以看到runAllTasks(ioTime * (100 - ioRatio) / ioRatio),通过ioRatio控制非io事件的执行时间。

如果ioRatio设置为100,那么Netty将不会限制非I/O任务的执行时间。这意味着在每次事件循环迭代中,Netty会尽可能快地执行所有的非I/O任务,而不会根据ioRatio来调整I/O事件处理时间。

Netty中对selectKeys优化是怎么回事

Netty中对selectedKeys的优化主要是针对Java NIO中SelectorselectedKeys()方法返回的SelectionKey集合的性能问题。在Java NIO中,每次调用Selectorselect()方法后,都需要处理selectedKeys()方法返回的集合中的每个SelectionKey,以确定哪些通道准备进行I/O操作。

在早期的Java版本中,selectedKeys()返回的集合是HashSet,这意味着每次调用selectedKeys()都会创建一个新的集合实例,并且在处理完选中的键后,需要手动清除已处理的键,以避免在下次选择操作时重复处理。这种操作方式在性能上是有开销的,尤其是在高负载下,频繁的集合创建和清除操作会显著影响性能。

Netty为了优化这一过程,采取了以下措施:

  1. 使用优化的集合:Netty使用了一个自定义的集合SelectedSelectionKeySet来替代JDK默认的HashSet。这个集合是一个数组,它避免了HashSet的性能开销,并且可以更快地遍历选中的键。

  2. 避免不必要的集合创建:Netty通过反射的方式将SelectorselectedKeyspublicSelectedKeys字段替换为自定义的SelectedSelectionKeySet实例,这样在每次调用select()方法后,不需要创建新的集合实例。

  3. 清除已处理键的优化:由于SelectedSelectionKeySet是专门为Netty的用途设计的,它可以在处理完选中的键后自动清除,无需手动操作,这进一步减少了性能开销。

以下是Netty中相关优化的简化代码示例:

if (selectedKeys != null && !selectedKeys.isEmpty()) {for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext(); ) {SelectionKey k = i.next();// 处理选中的键processSelectedKey(k);i.remove();}
}

在这个示例中,selectedKeysSelectedSelectionKeySet的实例,它在迭代过程中会自动清除已处理的键,这样在下一次select()调用时,就不会重复处理这些键。

通过这些优化,Netty显著提高了在高负载下处理selectedKeys的性能,减少了内存分配和垃圾收集的压力,从而提高了整个网络应用框架的性能和可扩展性。

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

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

相关文章

【408真题】2009-08

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

TMS320F280049 CLB模块--LUT4 OUTLUT(4)

LUT4 示意图如下&#xff1a; OUTLUT 示意图如下&#xff1a; 寄存器 参考文档&#xff1a; TMS320F28004x Real-Time Microcontrollers Technical Reference Manual (Rev. G)

【数据结构】图的应用---最小生成树(Prim,Kruskal)、最短路径(BFS,Dijkstra,Floyd)、拓扑排序、关键路径、有向无环图表达式

文章目录 5.图的应用5.1 最小生成树5.1.1 Prim算法5.1.2 Kruskal算法5.1.3 最小生成树代码A.邻接矩阵B.邻接表 5.2 最短路径5.2.1 BFS5.2.2 Dijkstra5.2.3 Floyd5.2.4 三种算法的比较 5.3 有向无环图描述表达式5.4 拓扑排序5.5 关键路径 5.图的应用 5.1 最小生成树 定义 对一个…

模型推导:BG/NBD(预测用户生命周期(CLV)模型)

CLV&#xff08;Customer Lifetime Value&#xff09;指的是客户生命周期价值&#xff0c;用以衡量客户在一段时间内对企业有多大的价值。企业对每个用户的流失与否、在未来时间是否会再次购买&#xff0c;还会再购买多少次才会流失等问题感兴趣&#xff0c;本文中的BG/NBD模型…

Linux|进程控制

进程创建 fork函数初识 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 进程调用fork&#xff0c;当…

AVL 树的理解和简单实现

目录 1. AVL 树 1.1. AVL 树的概念 1.2. AVL 树的性质 2. AVL 树的框架如下 2. AVL树的 插入 2.1. 平衡因子的更新 2.2.1. 平衡因子更新的第一种情况 2.2.2. 平衡因子更新的第二种情况 2.2.3. 平衡因子更新的第三种情况 2.2.4. 平衡因子更新的代码框架如下 2.2. AV…

《C++学习笔记---初阶篇6》---string类 上

目录 1. 为什么要学习string类 1.1 C语言中的字符串 2. 标准库中的string类 2.1 string类(了解) 2.2 string类的常用接口说明 2.2.1. string类对象的常见构造 2.2.2. string类对象的容量操作 2.2.3.再次探讨reserve与resize 2.2.4.string类对象的访问及遍历操作 2.2.5…

5KVA电力高频逆变器DU5000HD不间断电源DU3000HD

UPS电力高频逆变器DU3000HD不间断电源模块DU5000HD&#xff0c;单机版2KVA逆变电源DU2000HD&#xff0c;并机版2KVA逆变器DU2000H&#xff0c;3KVA逆变装置DU3000H&#xff0c;DU5000H&#xff0c;IV2000HD-2&#xff0c;IV3000HD-2&#xff0c;IV5000HD-2&#xff0c;IV2000H-…

基于51单片机音乐倒计时倒数计数器设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机音乐倒计时设计( proteus仿真程序设计报告原理图讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0090 1. 主要功能&#xff1a; 基于51单片机的音乐倒计时器设计 设计内…

47.乐理基础-音符的组合方式-连线

连线与延音线长得一模一样 它们的区别就是延音线的第三点&#xff0c;延音线必须连接相同的音 连线在百分之九十九的情况下&#xff0c;连接的是不同的音&#xff0c;如下图的对比&#xff0c;连线里的百分之1&#xff0c;以现在的知识无法理解&#xff0c;后续再写 在乐谱中遇…

解决在C#中方向键对控件焦点的控制

不要犹豫直接把下面这个程序复制进去就好了&#xff0c;不用担心0个引用&#xff0c;哈哈&#xff0c;可以的 public partial class MainForm : Form {public MainForm(){InitializeComponent();}protected override bool ProcessDialogKey(Keys keyData){// 检查是否是方向键…

Spring Boot 自动装配

本篇主要介绍Spring Boot 自动装配的相关内容。 目录 一、什么是自动装配 二、Bean的扫描方式 ComponentScan Import ImportSelector接口 三、Spring Boot自动装配原理 一、什么是自动装配 在我们在创建Spring Boot项目时往往会根据项目需求&#xff0c;引入很多第三方…

本地vite启动的vue项目使用nginx代理

前提&#xff1a; 必须在同一网段或者相同的局域网&#xff01;&#xff01;&#xff01; nginx下载通道&#xff1a; https://nginx.org/en/download.html 步骤&#xff1a; 1、最好下载稳定版本&#xff1a; 2、下载后直接解压&#xff08;注意&#xff1a;解压后不要放…

利用PS在不伤背景的前提下根据颜色去除图像上不想要的内容

下面为一个例子&#xff0c;去除图像上红色的虚线 Step1.用套索工具框选带有颜色的部分 Step2.切换到魔术棒工具&#xff0c;上端选项中&#xff0c;点击与选区交叉&#xff0c;连续这一项不要勾选 Step3.在需要去除的部分点击一下即可在框选范围内选中所有同颜色的区域&#x…

零基础学MySQL

1. 零基础学MySQL 1.1 数据库简介 1.1.1 数据库三层结构 1. 所谓安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database manage system) 2. 一个数据库中可以创建多个表,以保存数据(信息)。 3. 数据…

手写Windows文件路径获取小工具

手写Windows文件路径获取小工具 目的 给Windows右键增加功能&#xff0c;右键任何文件&#xff08;夹&#xff09;显示复制文件路径的扩展。 效果展示 实现思路 右键调用&#xff0c;自身会把文件路径传递给被调用文件&#xff0c;被调用文件内只需将路径参数复制到剪贴板即…

个人微信api

简要描述&#xff1a; 退出群聊 请求URL&#xff1a; http://域名地址/quitChatRoom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/json Authorization&#xff1a;login接口返回 参数&#xff1a; 参数名 必选 类型 …

中国当代最具影响力的人物颜廷利:死神(死亡)并不可怕,可怕的是…

中国当代最具影响力的人物颜廷利&#xff1a;死神&#xff08;死亡&#xff09;并不可怕&#xff0c;可怕的是… 在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 作为汉语‘九’字&#x…

docker(二):Centos安装docker

文章目录 1、安装docker2、启动docker3、验证 官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 1、安装docker 下载依赖包 yum -y install gcc yum -y install gcc-c yum install -y yum-utils设置仓库 yum-config-manager --add-repo http://mirrors…

KAN 笔记

1 Title KAN: Kolmogorov–Arnold Networks&#xff08;Ziming Liu, Yixuan Wang, Sachin Vaidya, Fabian Ruehle, James Halverson, Marin Soljačić, Thomas Y. Hou, Max Tegmark&#xff09;【2024】 2 Conclusion Inspired by the Kolmogorov-Arnold representat…