Netty从0到1系列之EventLoop

『Java分布式系统开发:从理论到实践』征文活动 10w+人浏览 158人参与


推荐阅读:

【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 {
	@Override
    EventExecutor next();
    EventExecutorGroup parent(); // ✅ parent方法来看看自己属于哪个EventLoopGroup
    boolean 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() {
    @Override
    public void run() {
        System.out.println("This is executed asynchronously in the EventLoop thread: "
                + Thread.currentThread().getName());
        // 这里可以安全地操作与这个EventLoop关联的Channel
        channel.writeAndFlush("Data from task");
    }
});

1.5.2 定时任务: 延迟执行

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

1.5.3 固定速率执行任务

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

1.5.4 固定延迟定时任务

// 4. 固定延迟定时任务(等待任务执行完成后,再延迟)
eventLoop.scheduleWithFixedDelay(new Runnable() {
    @Override
    public 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. 获取一个EventLoop
            EventLoop 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>() {
                        @Override
                        protected 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 实现高性能网络通信的“心脏” —— 它通过 无锁串行化统一事件循环,将复杂的并发问题转化为简单的串行处理,是现代异步网络框架的典范设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值