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

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的性能,减少了内存分配和垃圾收集的压力,从而提高了整个网络应用框架的性能和可扩展性。

相关文章:

Netty源码分析二NioEventLoop 剖析

剖析方向 NioEventLoop是一个重量级的类&#xff0c;其中涉及到的方法都有很复杂的继承关系&#xff0c;调用链&#xff0c;要想把源码全部过一遍工作量实在是太大了&#xff0c;于是小编就基于下面的这些常见的问题来对NioEventLoop的源码来进行剖析 1.Seletor何时创建 1.1Se…...

chatGLM或chatgpt:什么是tokens以及如何计算tokens长度?

token是什么? 简单的来说tokens就是大语言模型输入的向量数据,它是从原始的文本转化而来。 比如 输入:here is a text demo tokens为:[64790, 64792, 985, 323, 260, 2254, 16948] 解码:将tokens转化为文本 [‘[gMASK]’, ‘sop’, ‘▁here’, ‘▁is’, ‘▁a’, ‘▁…...

springcloudalibaba版本发布说明

版本发布说明 | https://sca.aliyun.com 2.2.x 分支 适配 Spring Boot 为 2.4&#xff0c;Spring Cloud Hoxton 版本及以下的 Spring Cloud Alibaba 版本按从新到旧排列如下表&#xff08;最新版本用*标记&#xff09;&#xff1a; Spring Cloud Alibaba VersionSpring Cloud…...

Obsidian/Typora设置图床

在obsidian中默认图片是保存在本地的&#xff0c;但是在要导出文档上传到网上时&#xff0c;由于图片保存在本地&#xff0c;会出现无法加载图片的问题。 这里引用的一段话&#xff1a; 这里使用picgo-core和gitee实现图床功能&#xff0c; 参考1&#xff1a; Ubuntu下PicGO配…...

【RAG论文】RAG中半结构化数据的解析和向量化方法

论文简介 论文题目&#xff1a; 《A Method for Parsing and Vectorization of Semi-structured Data used in Retrieval Augmented Generation》 论文链接&#xff1a; https://arxiv.org/abs/2405.03989 代码: https://github.com/linancn/TianGong-AI-Unstructure/tree/m…...

git提交代码异常报错error:bad signature 0x00000000

报错信息 error:bad signature 0x00000000 异常原因 git 提交过程中异常关机或重启&#xff0c;造成当前项目工程中的.git/index 文件损坏&#xff0c;无法提交 解决步骤 删除.git/index文件 rm -f .git/index 重启git git reset...

【FFmpeg】调用ffmpeg库进行RTMP推流和拉流

【FFmpeg】调用ffmpeg库实现RTMP推流 1.FFmpeg编译2.RTMP服务器搭建3.调用FFmpeg库实现RTMP推流和拉流3.1 基本框架3.2 实现代码3.3 测试3.3.1 推流3.3.2 拉流 参考&#xff1a;雷霄骅博士, 调用ffmpeg库进行RTMP推流 示例工程 【FFmpeg】调用FFmpeg库实现264软编 【FFmpeg】…...

Multisim 14 常见电子仪器的使用和Multisim的使用

multisim multisim&#xff0c;即电子电路仿真设计软件。Multisim是美国国家仪器&#xff08;NI&#xff09;有限公司推出的以Windows为基础的仿真工具&#xff0c;适用于板级的模拟/数字电路板的设计工作。它包含了电路原理图的图形输入、电路硬件描述语言输入方式&#xff0…...

【2024高校网络安全管理运维赛】巨细记录!

2024高校网络安全管理运维赛 文章目录 2024高校网络安全管理运维赛MISC签到考点&#xff1a;动态图片分帧提取 easyshell考点&#xff1a;流量分析 冰蝎3.0 Webphpsql考点&#xff1a;sql万能钥匙 fileit考点&#xff1a;xml注入 外带 Cryptosecretbit考点&#xff1a;代码阅读…...

Nuxt.js实战:Vue.js的服务器端渲染框架

创建Nuxt.js项目 首先&#xff0c;确保你已经安装了Node.js和yarn或npm。然后&#xff0c;通过命令行创建一个新的Nuxt.js项目&#xff1a; yarn create nuxt-app my-nuxt-project cd my-nuxt-project在创建过程中&#xff0c;你可以选择是否需要UI框架、预处理器等选项&…...

提高Rust安装与更新的速度

一、背景 因为rust安装过程中&#xff0c;默认的下载服务器为crates.io&#xff0c;这是一个国外的服务器&#xff0c;国内用户使用时&#xff0c;下载与更新的速度非常慢&#xff0c;因此&#xff0c;我们需要使用一个国内的服务器来提高下载与更新的速度。 本文推荐使用字节…...

【linux软件基础知识】内核代码中的就绪队列简化示例

在内核代码中,就绪队列通常使用允许高效插入和删除进程的数据结构来表示。 用于表示就绪队列的一种常见数据结构是链表。 以下是如何使用链表在内核代码中表示就绪队列的简化示例: struct task_struct {// Process control block (PCB) fields// ...struct task_struct *nex…...

《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…...

mysql中的页和行

页 行即表中的真实行&#xff0c;‘行式数据库’的由来 虽然MySQL的数据文件&#xff08;例如.ibd文件&#xff09;中的数据页在物理上是通过链表连接的&#xff0c;但是在逻辑上&#xff0c;MySQL使用B树来组织和访问数据。 行&#xff1a;主要是dynamic类型...

Vim常用快捷键

这个是我的草稿本记录一下防止丢失&#xff0c;以后有时间进行整理 0 或功能键[Home]这是数字『 0 』&#xff1a;移动到这一行的最前面字符处 (常用)$ 或功能键[End]移动到这一行的最后面字符处(常用)G移动到这个档案的最后一行(常用)nGn 为数字。移动到这个档案的第 n 行。例…...

力扣题目汇总分析 利用树形DP解决问题

树里 任意两个节点之间的问题。而不是根节点到叶子节点的问题或者是父节点到子节点的问题。通通一个套路&#xff0c;即利用543的解题思路。 543.二叉树的直径 分析 明确&#xff1a;二叉树的 直径 是指树中任意两个节点之间最长路径的 长度。两个节点之间的最长路径是他们之…...

GO语言核心30讲 实战与应用 (第二部分)

原站地址&#xff1a;Go语言核心36讲_Golang_Go语言-极客时间 一、sync.WaitGroup和sync.Once 1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。 2. WaitGroup类型有三个指针方法&#xff1a;Wait、Add和Done&#xff0c;以及内部有一个计数器。 (1) Wa…...

linux设置挂载指定的usb,自动挂载

一、设置指定的USB 在Linux系统中&#xff0c;如果您只想让系统挂载特定的USB设备&#xff0c;而忽略其他的USB设备&#xff0c;可以通过创建自定义的udev规则来实现。以下是设置系统只能挂载指定USB设备的基本步骤&#xff1a; 确定USB设备的属性&#xff1a; 首先&#xff0…...

简站WordPress主题

简站WordPress主题是一种专为建立网站而设计的WordPress模板&#xff0c;它旨在简化网站建设过程&#xff0c;使得用户能够更容易地创建和管理自己的网站。简站WordPress主题具有以下特点&#xff1a; 易用性&#xff1a;简站WordPress主题被设计为简单易用&#xff0c;适合各…...

is和==的关系

Python中is和的关系 is判断两个变量是不是指的是同一个内存地址&#xff0c;也就是通过id()函数判断 判断两个变量的值是不是相同 a [1, 2, 3, 4] b [1, 2, 3, 4] print(id(a)) # 2298268712768 print(id(b)) # 2298269716992 print(a is b) # False print(a b) # Tr…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...

全面解析数据库:从基础概念到前沿应用​

在数字化时代&#xff0c;数据已成为企业和社会发展的核心资产&#xff0c;而数据库作为存储、管理和处理数据的关键工具&#xff0c;在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理&#xff0c;到社交网络的用户数据存储&#xff0c;再到金融行业的交易记录处理&a…...

Java高级 |【实验八】springboot 使用Websocket

隶属文章&#xff1a;Java高级 | &#xff08;二十二&#xff09;Java常用类库-CSDN博客 系列文章&#xff1a;Java高级 | 【实验一】Springboot安装及测试 |最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot 静…...

在Android13上添加系统服务的好用例子

在Android13上添加一个自动的system service例子 留好&#xff0c;备用。 --- .../prebuilts/api/30.0/plat_pub_versioned.cil | 76 - .../prebuilts/api/31.0/plat_pub_versioned.cil | 94 - .../prebuilts/api/32.0/plat_pub_versioned.cil | 94 - frameworks/base/co…...