Skip to content

Netty EventLoop

EventLoop 是 Netty 的事件执行线程。一个 Channel 会绑定到一个 EventLoop,之后该 Channel 的 IO 事件通常都由同一个线程处理。

线程模型

mermaid
flowchart TD
    A[BossGroup] --> B[接收连接]
    B --> C[注册到WorkerGroup]
    C --> D[EventLoop 1]
    C --> E[EventLoop 2]
    C --> F[EventLoop 3]
    D --> G[处理Channel读写事件]
    E --> G
    F --> G

EventLoop 做什么

  1. 监听 Channel 的读写事件。
  2. 执行 ChannelPipeline 中的 Handler。
  3. 执行用户提交的普通任务。
  4. 执行定时任务,例如心跳检测。

核心原理

EventLoop 可以理解成一个不断循环的事件处理器。它不是只处理网络读写,还会处理普通任务和定时任务,所以它既是 IO 线程,也是该 Channel 的任务执行入口。

mermaid
flowchart TD
    A["EventLoop 循环开始"] --> B["Selector 等待 IO 就绪事件"]
    B --> C["处理可读 / 可写 / 关闭事件"]
    C --> D["触发 ChannelPipeline"]
    D --> E["执行普通任务队列"]
    E --> F["执行定时任务队列"]
    F --> A

为什么一个 Channel 要绑定同一个 EventLoop:同一个连接的读写事件按顺序进入同一个线程,可以减少锁竞争,也能避免同一条连接上的消息被多个线程同时处理导致顺序错乱。代价是这个线程一旦被阻塞,它负责的所有 Channel 都会被影响。

为什么不能阻塞

一个 EventLoop 会服务多个 Channel。如果在 Handler 中执行慢 SQL、远程调用或大文件处理,这个 EventLoop 上的其他连接也会被拖慢。

mermaid
flowchart TD
    A[IO事件进入Handler] --> B{是否耗时任务}
    B -- 否 --> C[EventLoop直接处理]
    B -- 是 --> D[投递到业务线程池]
    D --> E[异步写回结果]

如果不这样做,常见后果是:CPU 不一定很高,但请求延迟突然升高;少量慢请求把同一 EventLoop 上的其他连接拖住;心跳包不能及时处理,客户端误以为服务端掉线。

开发建议

场景建议
轻量协议解析放在 EventLoop
数据库查询投递到业务线程池
远程 HTTP 调用使用异步客户端或业务线程池
心跳超时使用 Netty 定时任务
共享状态尽量按 Channel 维度保存,减少锁

常见问题

如果 CPU 不高但请求延迟很大,重点检查 EventLoop 是否被阻塞。可以通过线程栈查看 nioEventLoopGroup 线程是否卡在业务方法、日志输出或同步 IO 上。

排查顺序建议:

  1. 先看线程栈,确认 nioEventLoopGroup 是否卡在业务类、日志类、数据库驱动或 HTTP 客户端。
  2. 再看单个 Handler 是否执行了阻塞调用。
  3. 再看写队列是否堆积,尤其是客户端接收慢时。
  4. 最后看 EventLoop 数量和 CPU 核数是否匹配。

代码 Demo:耗时任务投递到业务线程池

java
public class BizHandler extends SimpleChannelInboundHandler<String> {
    private final ExecutorService bizPool = Executors.newFixedThreadPool(16);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        bizPool.submit(() -> {
            String result = queryDatabaseOrCallRemoteApi(msg);
            ctx.executor().execute(() -> ctx.writeAndFlush(result));
        });
    }

    private String queryDatabaseOrCallRemoteApi(String msg) {
        return "result: " + msg;
    }
}

耗时逻辑在线程池执行,写回结果时再切回 Channel 所属的 EventLoop。这样可以避免阻塞 EventLoop 上的其他连接。