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 和任务而优化的、加强版的单线程执行器。
🌟 核心职责:
- 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 事件和异步任务的执行,下图清晰地展示了这一过程:
这个循环是 Netty 高性能的基石,它确保了:
- I/O 高响应性:优先处理就绪的 I/O 事件,保证网络通信的低延迟。
- 任务公平性:在 I/O 事件的间隙处理异步任务,防止任务饿死。
- 资源高效利用:在没有任务和 I/O 事件时,线程会优雅地阻塞在
select()
上,避免空转消耗 CPU。
1.3 EventLoop 与 EventLoopGroup 的关系
-
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);
关键点:
- 所有通过
execute
或schedule
提交的任务,都会被放入该 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 实现高性能网络通信的“心脏” —— 它通过 无锁串行化 和 统一事件循环,将复杂的并发问题转化为简单的串行处理,是现代异步网络框架的典范设计。