本文目录导读:

- Reactor 模式回顾
- Netty 的 Reactor 线程模型:主从多线程模型
- Netty 的核心组件与模型映射
- 性能优化:无锁串行化设计
- 代码实战:快速搭建一个 Echo 服务器
- 面试常见问题 & 面试官考察点
- 深入学习建议
Netty 的线程模型是其高性能的核心,它基于 Reactor 模式 进行了优化和实现,理解 Netty 的线程模型,主要就是理解 Boss Group 和 Worker Group 这两个线程组以及它们的协作方式。
下面我将从 Reactor 基础 到 Netty 的具体实现,再到 编码实战 为你进行详细讲解。
Reactor 模式回顾
Reactor 模式是一种事件驱动模型,它将 请求处理 和 I/O 就绪事件 分离开来。
- 核心角色:
- Reactor:负责监听和分发事件(Selector)。
- Handler:负责处理具体的事件(业务逻辑)。
- 核心思想:一个或多个线程(Reactor)负责监听大量客户端的连接/读写事件,然后分发给对应的工作线程(Handler)去处理,避免了传统 BIO 中一个连接一个线程的资源浪费。
Netty 的 Reactor 线程模型:主从多线程模型
Netty 采用了 主从 Reactor 多线程模型(在源码中叫 ReactorThreadModel),它包含两组线程池:
-
Boss Group(主 Reactor):
- 通常由 1 个或少量线程组成。
- 职责:负责监听 客户端的连接请求(处理
OP_ACCEPT事件)。 - 流程:Boss 线程接受到一个新的连接后,会将其注册到 Worker Group 的一个 NioEventLoop 上。
-
Worker Group(从 Reactor):
- 通常由多个线程(默认:CPU核心数 * 2)组成。
- 职责:负责处理连接的 读写 I/O 事件(
OP_READ、OP_WRITE)。 - 流程:Worker 线程负责从 SocketChannel 中读取数据、解码、执行业务逻辑(或者将任务提交给业务线程池)、编码、写回数据。
为什么这样做?
- 高并发下连接处理:Boss 线程只负责 accept,非常轻量,不会阻塞在读写上,可以快速处理大量并发连接。
- 读写负载均衡:Worker 线程组可以水平扩展,每个 Worker 线程管理一个 Selector,负责一批 Channel 的读写,提高了 I/O 处理能力。
Netty 的核心组件与模型映射
- EventLoopGroup:相当于线程池。
NioEventLoopGroup:最常用的实现。
- EventLoop:相当于一个线程(
Thread),内部包含一个 Selector 和一个 TaskQueue。- 一个
NioEventLoop就是一个 Reactor 线程。 - 它负责:
- 轮询 Selector 上的 I/O 事件(
select())。 - 处理 I/O 事件(
processSelectedKeys())。 - 执行队列中的普通任务(
runAllTasks())。 - 执行定时任务。
- 轮询 Selector 上的 I/O 事件(
- 一个
- Channel:代表一个网络连接(Socket)。
- ChannelPipeline:Channel 内的责任链,包含多个 ChannelHandler。
关键约束:
一个 Channel(连接)在其生命周期内,只会 唯一绑定 到一个 NioEventLoop(线程)上,这保证了同一 Socket 上的所有操作(读写、编码解码、业务逻辑)都在同一个线程中串行执行,无需加锁。
性能优化:无锁串行化设计
这是 Netty 设计的精髓:
- 无锁:同一个 Channel 的所有操作都在一个线程(EventLoop)上执行,不需要显式同步(
synchronized、ReentrantLock),避免了上下文切换和锁竞争的开销。 - 非阻塞:所有 I/O 操作都是 NIO 非阻塞的。
- 任务队列:如果其他线程想操作某个 Channel(例如服务器收到业务线程的回复),会将任务提交到该 Channel 绑定的 EventLoop 的任务队列中,EventLoop 会在下一次轮询时执行。
代码实战:快速搭建一个 Echo 服务器
下面是一个典型的 Netty 服务端代码,展示了 Boss & Worker 的配置。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;
public class NettyEchoServer {
private final int port;
public NettyEchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
// 1. 创建线程组
// Boss Group: 用于处理连接事件 (1个线程)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// Worker Group: 用于处理读写事件 (默认线程数 = CPU核数 * 2)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 2. 启动辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup) // 设置线程组
.channel(NioServerSocketChannel.class) // 指定 Channel 类型 (使用 NIO)
.option(ChannelOption.SO_BACKLOG, 128) // 设置 TCP 属性
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加自定义处理器到 Pipeline
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 3. 绑定端口并启动
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("Echo 服务器启动,端口: " + port);
// 4. 等待服务器关闭
future.channel().closeFuture().sync();
} finally {
// 5. 优雅关闭
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
// 自定义处理器(简单回显)
static class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("收到客户端消息: " + in.toString(CharsetUtil.UTF_8));
// 写回数据 (此时会在当前 Worker 线程上执行)
ctx.writeAndFlush(in);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
public static void main(String[] args) throws InterruptedException {
new NettyEchoServer(8080).start();
}
}
面试常见问题 & 面试官考察点
-
Netty 线程模型是什么?请画出它的架构图。
- 你应该立即回答:主从多线程 Reactor 模型,Boss Group(1个线程)负责 Accept,Worker Group(多线程)负责 Read/Write,一个 Channel 只会绑定一个 Worker 线程。
-
为什么 Netty 要设计成无锁串行化?
- 你应该回答:避免线程上下文切换和锁竞争的开销,一个 Pipeline 里的所有 Handler 都在同一个线程(EventLoop)上执行,保证了 Channel 级别的线程安全。
-
如何配置线程数量?
- Boss Group:通常为 1(或者 CPU 核数),因为只处理 accept。
- Worker Group:通常为
CPU核数 * 2(Netty默认),因为 I/O 操作是 CPU 密集型与 I/O 等待混合的,如果业务中有大量计算或阻塞操作,不建议直接在 Worker 线程里执行,应该使用独立的业务线程池,通过ChannelHandlerContext.channel().eventLoop().execute()提交任务或使用DefaultEventExecutorGroup。
-
如果一个 Worker 线程因为业务代码执行了
Thread.sleep()导致阻塞,会发生什么?- 核心考点:EventLoop 是一个单线程的 Reactor,如果它阻塞了,那么它管理的所有 Channel 都无法进行读写,整个服务会瘫痪(假死)。绝对禁止在 Netty 的 I/O 线程中执行耗时或阻塞操作,必须通过
ChannelHandlerContext.executor()或独立的业务线程池异步处理。
- 核心考点:EventLoop 是一个单线程的 Reactor,如果它阻塞了,那么它管理的所有 Channel 都无法进行读写,整个服务会瘫痪(假死)。绝对禁止在 Netty 的 I/O 线程中执行耗时或阻塞操作,必须通过
-
EventLoop 和 EventLoopGroup 的关系是什么?
EventLoopGroup是线程池,EventLoop是线程本身,一个EventLoopGroup包含多个EventLoop,当有新连接进来时,Boss 线程会选择一个 Worker 线程(通过轮询算法,如Chooser.next())来负责这个连接。
深入学习建议
- 源码层面:看看
NioEventLoop的run()方法,理解select()->processSelectedKeys()->runAllTasks()的循环。 - 优化层面:如果业务逻辑非常重(比如数据库操作),一定要把
ChannelHandler设置成@Sharable并用DefaultEventExecutorGroup或自定义线程池去执行,保持 I/O 线程的轻量化。 - 对比:理解一下 Reactor 单线程模型(Boss 和 Worker 是同一个线程)、多线程模型(Boss 单线程,Worker 多线程)和主从多线程模型(Boss 多线程,Worker 多线程),Netty 默认是主从多线程。
总结一句话:Netty 的线程模型通过将连接管理与读写分离,并利用无锁串行化设计,在保证线程安全的前提下,实现了极高的 I/O 性能。