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

Netty从0到1系列之EventLoop

文章目录

  • 一、EventLoop
    • 1.1 EventLoop 是什么?为什么需要它?
      • 1.1.1 核心概念
      • 1.1.2 要解决的问题:传统的并发模型缺陷
    • 1.2 EventLoop核心架构与工作原理
    • 1.3 EventLoop 与 EventLoopGroup 的关系
    • 1.4 EventLoop的继承体系
    • 1.5 EventLoop的核心工作: 任务调度
      • 1.5.1 立即执行异步任务 (Runnable)
      • 1.5.2 定时任务: 延迟执行
      • 1.5.3 固定速率执行任务
      • 1.5.4 固定延迟定时任务
      • 1.5.5 EventLoop基础使用
      • 1.5.6 EventLoop与Channel绑定
    • 1.6 EventLoop最佳实践
    • 1.7 EventLoop优缺点总结
    • 1.8 EventLoop核心价值
    • 1.9 一句话总结


推荐阅读:

【01】Netty从0到1系列之I/O模型
【02】Netty从0到1系列之NIO
【03】Netty从0到1系列之Selector
【04】Netty从0到1系列之Channel
【05】Netty从0到1系列之Buffer(上)
【06】Netty从0到1系列之Buffer(下)
【07】Netty从0到1系列之零拷贝技术
【08】Netty从0到1系列之整体架构、入门程序


一、EventLoop

1.1 EventLoop 是什么?为什么需要它?

1.1.1 核心概念

EventLoop 是 Netty 的核心执行单元。它的名字完美地描述了它的工作:

  • Event:它负责处理各种 I/O 事件(如数据可读、连接就绪、用户任务)。
  • Loop:它在一个无限循环中运行,不断地等待事件、处理事件。

你可以将它理解为一个专为处理 Channel I/O 和任务而优化的、加强版的单线程执行器

Netty 架构
EventLoopGroup
EventLoop-1
EventLoop-2
EventLoop-N
Channel-1
Channel-2
Channel-3
Channel-4

🌟 核心职责

  • I/O 事件轮询select
  • I/O 事件处理read/write/accept
  • 任务执行Runnable 任务队列)
  • 定时任务调度
  • 保证线程安全(无锁串行化)

1.1.2 要解决的问题:传统的并发模型缺陷

在传统的“一个连接,一个线程”(BIO)模型中,当连接数暴涨时,线程数量也随之暴涨,导致:

  • 巨大的内存消耗:每个线程都需要独立的栈内存(通常1MB左右)。
  • 巨大的 CPU 开销:线程上下文切换会消耗大量 CPU 资源。
  • 系统资源耗尽:最终系统无法创建新线程,性能急剧下降。

EventLoop 的解决方案:

  • 使用少量线程(通常为核心数的两倍)来管理海量连接
  • 每个 EventLoop 绑定一个线程,负责处理多个 Channel 上的所有事件。
  • 这实现了高效的资源利用和无锁化的串行设计,从根本上解决了传统模型的缺陷。

1.2 EventLoop核心架构与工作原理

EventLoop 的核心工作机制可以概括为一个高效的任务处理循环。其工作流程的精妙之处在于它如何平衡 I/O 事件和异步任务的执行,下图清晰地展示了这一过程:

Yes
No
EventLoop Thread Start
进入循环
检查是否有待处理任务?
处理所有异步任务
runAllTasks
Select 轮询 I/O 事件
selector.select
处理就绪的 I/O 事件
processSelectedKeys
再次检查任务队列
处理I/O事件
processSelectedKeys

这个循环是 Netty 高性能的基石,它确保了:

  1. I/O 高响应性:优先处理就绪的 I/O 事件,保证网络通信的低延迟。
  2. 任务公平性:在 I/O 事件的间隙处理异步任务,防止任务饿死。
  3. 资源高效利用:在没有任务和 I/O 事件时,线程会优雅地阻塞在 select() 上,避免空转消耗 CPU。

1.3 EventLoop 与 EventLoopGroup 的关系

绑定到 EventLoop 2 的 Channels
绑定到 EventLoop 1 的 Channels
EventLoopGroup (线程池)
Channel C
Channel A
Channel B
EventLoop 1
EventLoop 2
EventLoop 3
...
单一专属线程 Thread-1
单一专属线程 Thread-2
单一专属线程 Thread-3
  • EventLoopGroup:包含多个 EventLoop 的池子。Netty 通常创建两个 group:

    • BossGroup:负责接受新连接。连接接受后,它将 Channel 注册给 WorkerGroup 中的一个 EventLoop

    • WorkerGroup:负责处理已接受连接的 I/O 读写

  • EventLoop一个 EventLoop 在其生命周期内只绑定一个线程。反之,该线程也只服务于这个 EventLoop。

  • Channel一个 Channel 在其生命周期内只注册到一个 EventLoop。反之,一个 EventLoop 可以被注册给多个 Channel

这条规则是 Netty 实现无锁化和线程安全架构的基石! 它保证了对同一个 Channel 的所有操作始终由同一个线程串行执行,彻底避免了复杂的同步。

1.4 EventLoop的继承体系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

io.netty.util.concurrent-> SingleThreadEventExecutor-> SingleThreadEventLoop-> NioEventLoop / EpollEventLoop / ...
  • SingleThreadEventExecutor:封装了单一线程任务队列的核心逻辑。
  • SingleThreadEventLoop:增加了注册 Channel 和执行 I/O 操作的能力。
  • NioEventLoop:基于 Java NIO Selector 的具体实现,也是最常用的实现。

ScheduledExecutorService

public interface ScheduledExecutorService extends ExecutorService {// other code ... 
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OrderedEventExecutor

public interface OrderedEventExecutor extends EventExecutor {
}

EventExecutor

public interface EventExecutor extends EventExecutorGroup {@OverrideEventExecutor next();EventExecutorGroup parent(); // ✅ parent方法来看看自己属于哪个EventLoopGroupboolean inEventLoop();boolean inEventLoop(Thread thread); // ✅ 判断一个线程是否属于当前的EventLoop<V> Promise<V> newPromise();<V> ProgressivePromise<V> newProgressivePromise();<V> Future<V> newSucceededFuture(V result);<V> Future<V> newFailedFuture(Throwable cause);
}

[!note]

🥭总结

  • EventLoop继承自JUC包下的ScheduledExecutorService, 因此包含了线程池中所有的方法.
  • EventLoop继承自Netty自己的OrderedEventExecutor
    • 提供了 boolean inEventLoop(Thread thread) 方法,判断一个线程是否属于此 EventLoop
    • 提供了 parent 方法来看看自己属于哪个 EventLoopGroup

1.5 EventLoop的核心工作: 任务调度

EventLoop 继承自 ScheduledExecutorService,因此它具备 JDK 线程池的所有能力,并且更加强大。

Channel channel = ...;
EventLoop eventLoop = channel.eventLoop();

1.5.1 立即执行异步任务 (Runnable)

// 1. 立即执行异步任务 (Runnable)
eventLoop.execute(new Runnable() {@Overridepublic void run() {System.out.println("This is executed asynchronously in the EventLoop thread: "+ Thread.currentThread().getName());// 这里可以安全地操作与这个EventLoop关联的Channelchannel.writeAndFlush("Data from task");}
});

1.5.2 定时任务: 延迟执行

// 2. 定时任务:延迟执行
ScheduledFuture<?> future = eventLoop.schedule(new Runnable() {@Overridepublic void run() {System.out.println("Executed after 5 seconds delay");}
}, 5, TimeUnit.SECONDS); // 延迟5秒

1.5.3 固定速率执行任务

// 3. 固定速率定时任务(忽略任务执行时间)
eventLoop.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("Executed every 3 seconds");}
}, 1, 3, TimeUnit.SECONDS); // 初始延迟1秒,之后每3秒一次

1.5.4 固定延迟定时任务

// 4. 固定延迟定时任务(等待任务执行完成后,再延迟)
eventLoop.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000); // 模拟耗时任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Executed with 3 seconds delay after the previous task finished");}
}, 1, 3, TimeUnit.SECONDS);

关键点:

  • 所有通过 executeschedule 提交的任务,都会被放入该 EventLoop 的任务队列中。
  • EventLoop 线程会在其运行循环中消费并执行这些任务。
  • 因为这些任务和在同一个 EventLoop 上处理的 I/O 事件是串行执行的,所以它们是线程安全的。

1.5.5 EventLoop基础使用

package cn.tcmeta.demo02;import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;import java.util.concurrent.TimeUnit;/*** @author: laoren* @description: EventLoop的基本使用* @version: 1.0.0*/
public class EventLoopExample {public static void main(String[] args) {// 创建一个NioEventLoopGroup(包含多个 EventLoop)NioEventLoopGroup group = new NioEventLoopGroup(2);try {// 2. 获取一个EventLoopEventLoop eventLoop = group.next();System.out.printf("线程名称: 【%s】 , -------  %s \n", Thread.currentThread().getName(), "");// 3. 提交一个普通任务eventLoop.execute(() -> {System.out.printf("线程名称: 【%s】 , ----------: %s \n", Thread.currentThread().getName(), "✅");});// 4. 提交定时任务eventLoop.schedule(() -> {System.out.printf("线程名称: 【%s】 , ----------: %s \n", Thread.currentThread().getName(), "🥭");}, 2, TimeUnit.SECONDS);// 5. 提交一个周期性任务eventLoop.scheduleAtFixedRate(() -> {System.out.printf("线程名称: 【%s】 , ----------: %s \n", Thread.currentThread().getName(), "🎁");}, 0, 1, TimeUnit.SECONDS);try {TimeUnit.MILLISECONDS.sleep(5000);}catch (InterruptedException e){e.printStackTrace();}}catch (Exception e){e.printStackTrace();}finally {group.shutdownGracefully(); // 关闭线程池}}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.5.6 EventLoop与Channel绑定

package cn.tcmeta.demo02;import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;/*** @author: laoren* @date: 2025/9/1 14:19* @description: Channel与EventLoop绑定* @version: 1.0.0*/
public class ChannelEventLoopBinding {public static void main(String[] args) {NioEventLoopGroup boosGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerGroup = new NioEventLoopGroup(2);try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(boosGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {EventLoop eventLoop = ch.eventLoop();System.out.println("🔗 Channel " + ch.id() +" bound to EventLoop thread: " +Thread.currentThread().getName());// 在 EventLoop 线程中执行任务eventLoop.execute(() -> {System.out.println("⚡ Channel " + ch.id() +" executing task in " +Thread.currentThread().getName());// 模拟响应ch.writeAndFlush(Unpooled.copiedBuffer("Hello from " + Thread.currentThread().getName() + "\n",java.nio.charset.StandardCharsets.UTF_8));});}});ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("🚀 Server started at port 8080");future.channel().closeFuture().sync();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {boosGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.6 EventLoop最佳实践

✅ 推荐实践

实践说明
合理设置线程数EventLoopGroup 线程数 = CPU 核心数 × 2
避免阻塞 EventLoop不要在 ChannelHandler 中执行 Thread.sleep()、数据库查询等耗时操作
使用独立业务线程池耗时任务提交到业务线程池
使用 eventLoop().execute()确保代码在 I/O 线程执行
优雅关闭调用 shutdownGracefully()

⚠️ 常见错误

// ❌ 错误:阻塞 I/O 线程
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {Thread.sleep(5000); // 严重阻塞!ctx.writeAndFlush(msg);
}// ✅ 正确:提交到业务线程池
private final ExecutorService businessPool = Executors.newFixedThreadPool(10);@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {businessPool.execute(() -> {// 耗时业务processBusiness(msg);ctx.writeAndFlush(result);});
}

1.7 EventLoop优缺点总结

✅ 优点

优点说明
高性能无锁串行化,避免锁竞争
高并发单线程处理多连接,支持百万级并发
线程安全同一 Channel 的操作由同一线程执行
资源高效线程数可控,减少上下文切换
任务统一调度I/O、任务、定时任务统一处理

❌ 缺点

缺点说明
调试困难异步编程,堆栈不直观
阻塞风险一旦 I/O 线程阻塞,整个 Channel 挂起
学习成本高需理解事件驱动、Reactor 模式
内存管理复杂ByteBuf 需手动释放

1.8 EventLoop核心价值

维度说明
核心思想一个线程一个事件循环,串行化处理
关键技术Reactor 模式、无锁设计、任务队列
性能优势低延迟、高吞吐、高并发
设计精髓“让 I/O 线程只做 I/O 事”
适用场景所有 Netty 网络应用的基础

1.9 一句话总结

💡 一句话总结

EventLoop 是 Netty 实现高性能网络通信的“心脏” —— 它通过 无锁串行化统一事件循环,将复杂的并发问题转化为简单的串行处理,是现代异步网络框架的典范设计。

http://www.xdnf.cn/news/20520.html

相关文章:

  • 魅族 Note 16 解锁 BL 及 Root 官方刷机包下载Flyme 12.0.1.5A 型号 M521Q
  • 基于SVN搭建企业内部知识库系统实践
  • 试用电子实验记录本ELN的经验之谈
  • 【算法】92.翻转链表Ⅱ--通俗讲解
  • Vue 3项目中引用ECharts并设计多种图表组件的实现方案
  • 行政区划编码树形题解
  • 09_多态
  • `IntersectionObserver`延迟加载不在首屏的自动播放视频/图片/埋点/
  • Boost电路:稳态和小信号分析
  • Linux匿名管道和命名管道以及共享内存
  • C++并发编程指南 递归锁 介绍
  • Kimi K2-0905 256K 上下文 API 状态管理优化教程
  • 2.虚拟内存:分页、分段、页面置换算法
  • 分享一个基于Python+大数据的房地产一手房成交数据关联分析与可视化系统,基于机器学习的深圳房产价格走势分析与预测系统
  • Embedding上限在哪里?- On the Theoretical Limitations of Embedding-Based Retrieval
  • AI产品经理面试宝典第86天:提示词设计核心原则与面试应答策略
  • 《sklearn机器学习——聚类性能指标》Calinski-Harabaz 指数
  • Wisdom SSH 是一款搭载强大 AI 助手的工具,能显著简化服务器配置管理流程。
  • SSH服务远程安全登录
  • Linux系统shell脚本(四)
  • CodeSandbox Desktop:零配置项目启动工具,实现项目环境隔离与Github无缝同步
  • AI大模型应用研发工程师面试知识准备目录
  • 苍穹外卖优化-续
  • Java包装类型
  • Git 长命令变短:一键设置别名
  • Linux以太网模块
  • 【嵌入式】【科普】AUTOSAR学习路径
  • 《无畏契约》游戏报错“缺少DirectX”?5种解决方案(附DirectX修复工具)
  • 基于单片机智能行李箱设计
  • 云手机运行流畅,秒开不卡顿