Netty4的EventLoop和线程模型原理解析

简介: 线程模型指定了os、编程语言、框架或应用程序的上下文中的线程管理的关键方面。如何、何时创建线程将对应用程序代码执行产生显著影响,开发人员必须理解不同模型之间的权衡。而 Netty 的线程模型强大又易用,正如 Netty 的宗旨:简化你的应用程序代码,同时最大限度提高性能和可维护性。

1 线程模型血泪史

早期使用多线程是按需创建启动新 Thread 执行并发的任务单元,但这在高负载下表现很差。Java5 引入Executor,其线程池通过缓存和重用 Thread 极大提升性能。


基本的线程池化模式:

  1. 从池的空闲线程列表中选择一个 Thread,并被指派运行一个已提交的任务(Runnable 实现)
  2. 任务完成时,将该 Thread 返回给该列表,使其被重用


Executor 的执行逻辑

1.png

虽然池化/重用线程相对为每个任务都创建、销毁线程是一种进步,但它并不能消除上下文切换的开销,其随线程数的增加而很快变得明显,并在高负载下更严重。此外,仅由于APP整体复杂性或并发需求,在项目生命周期内也可能会出现其他和线程相关问题。总之多线程处理很复杂,但 Netty 简化之!

2 EventLoop 接口

网络框架的基本功能

运行任务来处理在连接的生命周期内发生的事件。在代码中称为事件循环,即 io.netty.channel.EventLoop

1.png

在事件循环中执行任务

1.png

EventLoop 是协同设计的一部分,采用了两个基本 API:并发和网络编程。

  1. io.netty.util.concurrent 包基于 JDK 的juc包而构建,以提供线程执行器
  2. io.netty.channel 包中的类,为了与 Channel 的事件交互,扩展了这些接口/类

EventLoop 的类层次结构

1.png

该模型中,一个 EventLoop 由一个永不变的 Thread 驱动,同时任务(RunnableCallable)可直接提交给 EventLoop 的实现,以立即或调度执行。


根据配置和可用核的不同,可能会创建多个 EventLoop 实例,以优化资源使用,且单个 EventLoop 可能会被指派以服务多个 Channel


EventLoop继承ScheduledExecutorService时,只定义了一个方法 parent() (重写 EventExecutorEventExecutorGroup#parent())。

1.png

该方法用以返回到当前EventLoop实例所属的EventLoopGroup的引用。

事件/任务的执行顺序

事件和任务以 FIFO 顺序执行。这可通过保证字节内容总是按正确顺序被处理,消除数据被损坏的可能性。

Netty4 的 I/O 和事件处理

由 I/O 操作触发的事件将流经安装了一或多个ChannelHandlerChannelPipeline

传播这些事件的方法调用可随后被 ChannelHandler拦截并可按需处理事件。

事件的性质决定它将被如何处理:


  • 可能将数据从网络栈中传递到你的APP
  • 逆向操作
  • 执行一些截然不同的操作


但事件的处理逻辑须高可复用,以处理所有可能的用例。因此在Netty4,所有I/O操作和事件都由已被分配给EventLoopThread处理(注意这里是“处理”而非“触发”,因其中的写操作可从外部的任意线程触发)

Netty3 的 I/O 操作

在旧版线程模型仅保证:

  • 入站(之前称为上游)事件会在 I/O 线程(Netty 4 中的 EventLoop)中执行
  • 所有出站(下游)事件都由调用线程处理,其可能是 I/O 线程也可能是其它线程


起初挺好,但已被发现有问题,因需在ChannelHandler中同步出站事件:不可能保证多线程不会在同时刻尝试访问出站事件。

例如,若你通过在不同线程中调用 Channel.write(),针对同一 Channel 同时触发出站的事件,就会发生这种情况。

当出站事件触发入站事件时,将导致另一个负面影响。当 Channel.write()导致异常时,需生成并触发一个 exceptionCaught 事件。但在 Netty3 的模型,因这是个入站事件,需在调用线程中执行代码,然后将事件移交给 I/O 线程去执行,这会带来额外上下文切换开销。


而 Netty4 的线程模型,在同一线程中处理某给定 EventLoop中所产生的所有事件,则解决了该问题。其提供了更简单的执行体系架构,并消除了在多ChannelHandler中需同步的必要(除任何可能需在多 Channel 中共享的)。

3 任务调度

当需要调度一个任务以延迟或周期执行时。


例如想注册一个在客户端连接 5 min后触发的任务:发送心跳到远程节点,以检查连接是否存活。若无响应,便知可关闭该 Channel。

3.1 JDK 任务调度

Java5 前,任务调度基于 java.util.Timer类,其使用一个后台 Thread 且具有与标准线程相同的限制。

后来JDK提供juc包,定义了ScheduledExecutorService接口:

image.png

虽然可选项不多(JDK提供该接口的唯一实现java.util.concurrent.ScheduledThreadPoolExecutor),但该实现足以应对大多场景:

使用 ScheduledExecutorService 在 60 秒的延迟之后执行一个任务

1.png

使用起来简单粗暴。

3.2 Netty#EventLoop 调度任务

JDK 的ScheduledExecutorService实现局限性

作为线程池管理的部分功能,将有额外线程创建:若有大量任务被密集调度,这将成为瓶颈。

ChannelEventLoop 实现任务调度解决了该问题:


EventLoop 调度任务,60s后Runnable实例由 ChannelEventLoop 执行

image.png

若要调度任务以每60s执行一次,使用 scheduleAtFixedRate()


使用 EventLoop 调度周期性的任务

1.png

EventLoop继承于ScheduledExecutorService,所以也提供了JDK实现的所有方法,包括之前的schedule()和scheduleAtFixedRate()。

要想取消或检查被调度任务的执行状态,可使用每个异步操作所返回的 ScheduledFuture,


使用 ScheduledFuture 取消任务

1.png

4 实现原理

4.1 线程管理

Netty线程模型的高性能取决于对当前执行的Thread的身份的确定(通过调用EventLoop#inEventLoop(Thread)实现负责处理一个Channel的整个生命周期内的所有事件)。

  • 若当前调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被直接执行
  • 否则,EventLoop 将调度该任务以稍后执行,并将它放到内部队列。当 EventLoop下次处理它的事件时,会执行队列中的那些任务/事件
    这也解释了任何 Thread 如何与 Channel 直接交互,而无需在 ChannelHandler 中额外同步。

每个 EventLoop 都有自已的任务队列(不像线程池共用一个任务队列并抢夺)。

  • EventLoop 调度任务的执行逻辑

1.png

禁止将一个长时间运行的任务放入执行队列,因它将阻塞需在同一线程上执行的其他任务!

若必须阻塞调用或执行长时间运行的任务,推荐使用专门的EventExecutor

除这种受限场景,传输所采用的不同事件处理实现,其线程模型也会严重影响排队的任务对整体系统性能影响。

4.2 EventLoop线程的分配

服务于 Channel 的 I/O 和事件的EventLoop 包含在 EventLoopGroup中。不同传输实现,EventLoop的创建、分配方式也不同。

异步传输

异步传输实现只使用少量 EventLoop 及和它们相关联的 Thread,且在当前线程模型,它们可能会被多个 Channel 共享。这使得可通过尽可能少的 Thread 支撑大量 Channel,而非每个 Channel 分配一个 Thread


一个 EventLoopGroup,具有3个固定大小 EventLoop(每个 EventLoop由一个 Thread 支撑)。在创建 EventLoopGroup时直接分配了EventLoop(以及支撑它们的 Thread),以确保在需要时它们可用:

1.png

EventLoopGroup 负责为每个新创建 Channel 分配一个 EventLoop。

当前实现中使用轮询的(round-robin)分配算法,一个EventLoop可能会被分配给多个 Channel。(这点在将来版本中可能变)。


一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期都使用这个EventLoop及相关联 Thread。这也能避免ChannelHandler实现中的线程安全问题。


EventLoop 的分配方式对 ThreadLocal 的使用影响

因一个EventLoop 通常会被用于支撑多个 Channel,所以对于所有相关联 Channel,ThreadLocal都将一样。这使得它对于实现状态追踪等功能来说是个糟糕选择。然而在一些无状态上下文中,它仍可被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件

阻塞传输

用于像 OIO(旧的阻塞 I/O)这样的其他传输的设计略有不同。


阻塞传输(如 OIO)的 EventLoop 分配方式,每个 Channel 都将被分配给一个 EventLoop及它的 Thread

image.png

如果你使用过 java.io 包的阻塞 I/O,可能就遇到过这种模型。


得到的保证是每个 Channel 的 I/O 事件都只会被一个 Thread(用于支撑该 Channel 的 EventLoop 的那个 Thread)处理。这也是另一个Netty 设计一致性的例子,这种设计上的一致性对 Netty 的可靠性和易用性做出了巨大贡献。


参考

  • 《Netty实战》
目录
相关文章
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
386 22
解析静态代理IP改善游戏体验的原理
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
948 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
1332 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1639 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
机器学习/深度学习 人工智能 算法
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
DeepSeek-R1 通过创新的训练策略实现了显著的成本降低,同时保持了卓越的模型性能。本文将详细分析其核心训练方法。
1565 11
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
13959 46
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
1112 2

推荐镜像

更多
  • DNS