Netty Reactor 模型:Boss、Worker 与 EventLoop
摘要
Netty 作为 Java 领域最流行的网络通信框架,其核心优势在于高效的 Reactor 线程模型。本文深入剖析 Netty 的 Reactor 模型实现,从 Boss、Worker 线程的职责划分,到 EventLoop 的事件循环机制,结合 Netty 4.1.x 版本的源码,全面揭示其高性能架构的设计精髓。
一、Reactor 模型概述
1.1 什么是 Reactor 模型
Reactor 模型是一种基于事件驱动的并发设计模式,通过单线程或多线程来处理多个并发连接的 I/O 事件。其核心思想是将 I/O 事件注册到多路复用器上,一旦有事件触发,就将事件分发到对应的事件处理器中执行。
1.2 Reactor 模型的三种形态
单 Reactor 单线程模型
连接
分发
分发
分发
客户端
Reactor线程
Handler1
Handler2
Handler3
特点:
- 一个线程处理所有连接的接受、读写和业务逻辑
- 实现简单,但性能瓶颈明显
- 无法充分利用多核 CPU
单 Reactor 多线程模型
连接
分发 I/O
分发 I/O
业务处理
业务处理
返回结果
返回结果
客户端
Reactor线程
Handler1
Handler2
线程池
特点:
- Reactor 线程只负责 I/O 事件分发
- 业务逻辑交给线程池处理
- I/O 操作和业务处理解耦
主从 Reactor 多线程模型
从Reactor
主Reactor
建立连接
分配连接
分配连接
分配连接
连接请求
读写请求
读写请求
读写请求
MainReactor
Acceptor
SubReactor1
SubReactor2
SubReactor3
客户端
特点:
- 主 Reactor:负责监听 Server Socket,建立连接
- 从 Reactor:负责已建立连接的 I/O 读写
- 多线程并发处理:充分利用多核 CPU 资源
- 高并发、高性能:Netty 采用此模型
1.3 Netty 对 Reactor 模型的实现
Netty 灵活支持上述三种模型:
| 模型类型 | Boss 线程数 | Worker 线程数 | 适用场景 |
|---|---|---|---|
| 单 Reactor 单线程 | 1 | 1 | 低并发、简单应用 |
| 单 Reactor 多线程 | 1 | 多个 | 中等并发 |
| 主从 Reactor 多线程 | 多个 | 多个 | 高并发、高性能场景 |
二、Boss 与 Worker 线程模型
2.1 Boss 和 Worker 的职责划分
在 Netty 的主从 Reactor 模型中:
Boss Group(主 Reactor)
- 核心职责 :负责处理客户端的连接请求(Accept 事件)
- 具体任务 :
- 监听服务端端口(ServerSocketChannel)
- 接收客户端连接(SocketChannel)
- 将新连接注册到 Worker Group 的某个 EventLoop 上
Worker Group(从 Reactor)
- 核心职责 :负责处理已建立连接的I/O 读写事件
- 具体任务 :
- 监听已连接通道的读写就绪事件
- 处理数据的读入和写出
- 执行用户定义的 ChannelHandler 业务逻辑
2.2 Boss 和 Worker 的协作流程
ChannelHandler Worker EventLoop Boss EventLoop 客户端 ChannelHandler Worker EventLoop Boss EventLoop 客户端 1. 发起连接请求 2. 监听到 Accept 事件 3. 注册新连接到 Worker 4. 连接建立完成 5. 发送数据 6. 触发 Read 事件 7. 调用 channelRead() 8. 处理完成,准备响应 9. 写出响应数据
2.3 源码分析:ServerBootstrap 的线程组配置
以下是 Netty 典型的服务端启动代码:
java
// 1. 创建 Boss 和 Worker 线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // Boss 线程数通常为 1
EventLoopGroup workerGroup = new NioEventLoopGroup(); // Worker 线程数默认为 CPU 核心数 * 2
try {
// 2. 创建服务器启动引导类
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) // 配置 Boss 和 Worker 线程组
.channel(NioServerSocketChannel.class) // 指定 Channel 类型
.childHandler(new ChannelInitializer<SocketChannel>() { // 配置处理器
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(new StringDecoder());
p.addLast(new StringEncoder());
p.addLast(new MyServerHandler());
}
});
// 3. 绑定端口并启动
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
关键点说明:
bossGroup线程数通常设为 1,因为 accept 事件处理很快workerGroup线程数建议设置为CPU 核心数 * 2childHandler配置的是 Worker 线程处理业务逻辑的 Handler
三、EventLoop:事件循环的核心引擎
3.1 EventLoop 的概念与继承体系
EventLoop 是 Netty Reactor 模型的核心接口,它继承自 EventExecutor,同时实现了 OrderedEventExecutor 接口。
<<interface>>
EventExecutor
+execute(Runnable task)
+submit(Runnable task)
<<interface>>
OrderedEventExecutor
+保证任务执行顺序
<<interface>>
EventLoop
+register(Channel channel)
+process() : 事件轮询
SingleThreadEventExecutor
#doStartThread()
#run()
SingleThreadEventLoop
#run() : 事件循环逻辑
NioEventLoop
-Selector selector
+processSelectedKeys()
3.2 EventLoop 的三大核心功能
- I/O 事件轮询:通过 Selector 监听通道事件
- 任务队列处理:执行非 I/O 的普通任务和定时任务
- 串行化执行:保证同一线程的任务顺序执行
3.3 EventLoop 的事件循环流程
是
否
是
否
是
否
是
否
EventLoop 启动
for 死循环
有任务吗?
执行任务队列
阻塞 Selector.select
有 I/O 事件?
处理 SelectedKeys
有任务?
需要优雅退出?
退出循环
3.4 源码分析:NioEventLoop 的核心实现
版本:Netty 4.1.86.Final
1. EventLoop 的初始化
java
// io.netty.channel.nio.NioEventLoopGroup#newChild
@Override
protected EventExecutor newChild(Executor executor, Object... args) throws Exception {
// ...省略参数准备
return new NioEventLoop(this, executor, selectorProvider, selectStrategyFactory,
rejectImmediately, args); // 创建 NioEventLoop 实例
}
2. NioEventLoop 构造函数
java
// io.netty.channel.nio.NioEventLoop#NioEventLoop
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectImmediately, Object... args) {
super(parent, executor, false, DEFAULT_MAX_PENDING_EXECUTOR_TASKS, rejectedExecutionHandler);
// ...省略部分代码
// 打开 Selector
this.provider = selectorProvider;
this.selector = openSelector(); // 关键:打开 JDK Selector
this.selectStrategy = strategy;
}
3. EventLoop 的核心 run() 方法
java
// io.netty.channel.nio.NioEventLoop#run
@Override
protected void run() {
int selectCnt = 0;
for (;;) { // 无限循环
try {
int strategy;
try {
// 计算选择策略:根据是否有任务决定是否阻塞
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// 阻塞式 Select,等待 I/O 事件
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // 没有定时任务,无限等待
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
strategy = select(curDeadlineNanos); // 阻塞 Select
}
} finally {
nextWakeupNanos.unset();
}
// ...省略部分代码
default:
}
} catch (IOException e) {
// ...省略异常处理
}
selectCnt++; // Select 计数器(用于解决 JDK epoll 空轮询 bug)
// 处理 I/O 事件
processSelectedKeys(); // 核心:处理就绪的 SelectionKey
// 处理任务队列
ranTasks = runAllTasks(); // 核心:执行普通任务和定时任务
if (ranTasks || strategy > 0) {
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for SelectorImpl.", selectCnt - 1);
}
selectCnt = 0;
} else if (unexpectedSelectorWakeup(selectCnt)) { // 处理 JDK epoll 空轮询 bug
selectCnt = 0;
// ...重建 Selector
}
} catch (CancelledKeyException e) {
// ...省略异常处理
} catch (Throwable t) {
handleLoopException(t);
}
// ...省略退出条件判断
}
}
3.5 EventLoop 与 Channel 的绑定关系
Netty 保证:一个 Channel 在其生命周期内只会绑定到一个 EventLoop 上,而一个 EventLoop 可以处理多个 Channel。
java
// io.netty.channel.AbstractChannel#AbstractChannel
protected AbstractChannel(Channel parent) {
this.parent = parent;
this.id = newId();
this.unsafe = newUnsafe();
this.pipeline = newChannelPipeline();
}
Channel 注册到 EventLoop 的过程:
java
// io.netty.channel.AbstractChannel.AbstractUnsafe#register0
private void register0(ChannelPromise promise) {
try {
// ...省略部分代码
// 将 Channel 注册到 EventLoop 的 Selector 上
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
// ...省略部分代码
} catch (Throwable e) {
// ...省略异常处理
}
}
关键特性:
| 特性 | 说明 |
|---|---|
| 线程绑定 | Channel 一旦注册,就固定在某个 EventLoop 的线程上执行 |
| 无锁化 | 同一个 EventLoop 内的所有操作都是串行执行的,无需加锁 |
| 负载均衡 | EventLoopGroup 通过轮询算法分配 Channel 到不同的 EventLoop |
四、TaskQueue:异步任务队列机制
4.1 TaskQueue 的作用
EventLoop 除了处理 I/O 事件,还需要执行异步任务:
- 普通任务:用户提交的 Runnable 任务
- 定时任务:ScheduledFutureTask
4.2 TaskQueue 的类型
java
// io.netty.util.concurrent.SingleThreadEventExecutor
private final Queue<Runnable> taskQueue;
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
// 添加任务到队列
addTask(task);
// 如果线程未启动,则启动线程
if (isShutdown()) {
reject();
}
if (addTaskWakesUp || wakesUpForTask(task)) {
wakeup(inEventLoop()); // 唤醒 Selector,确保任务及时执行
}
}
4.3 任务的执行时机
java
// io.netty.channel.nio.NioEventLoop#run
protected void run() {
for (;;) {
try {
// 1. 计算 Select 策略
// 2. 执行 Select 操作
// 3. 处理 I/O 事件
processSelectedKeys();
// 4. 执行任务队列(关键)
ranTasks = runAllTasks();
} catch (Throwable t) {
handleLoopException(t);
}
}
}
4.4 任务队列的执行策略
java
// io.netty.util.concurrent.SingleThreadEventExecutor#runAllTasks
protected boolean runAllTasks() {
assert inEventLoop();
boolean fetchedAll;
boolean ranAtLeastOne = false;
do {
// 1. 从定时任务队列中提取到期任务
fetchedAll = fetchFromScheduledTaskQueue();
// 2. 从普通任务队列中取任务并执行
Runnable task = pollTask();
if (task == null) {
return ranAtLeastOne;
}
// 3. 执行任务
safeExecute(task);
ranAtLeastOne = true;
} while (!fetchedAll); // 只要还有任务就继续执行
return true;
}
执行流程:
是
否
是
否
否
是
EventLoop.run
有 I/O 事件?
处理 I/O 事件
有任务?
提取定时任务
继续 Select
执行任务队列
任务队列空?
五、实战:自定义 EventLoop 与 Handler
5.1 自定义 Handler 示例
java
// 自定义入站处理器
@ChannelHandler.Sharable // 标记为可共享(无状态)
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("客户端连接: " + ctx.channel().remoteAddress());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String request = (String) msg;
System.out.println("收到消息: " + request);
// 异步响应
ctx.writeAndFlush("Hello, " + request);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
5.2 完整的服务端代码
java
public class NettyServer {
private static final int BOSS_THREADS = 1;
private static final int WORKER_THREADS = Runtime.getRuntime().availableProcessors() * 2;
public static void main(String[] args) throws Exception {
// 创建 Boss 和 Worker 线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(BOSS_THREADS);
EventLoopGroup workerGroup = new NioEventLoopGroup(WORKER_THREADS);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 添加编解码器
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
// 添加业务处理器
pipeline.addLast(new MyServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 连接队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持连接
// 绑定端口并启动
ChannelFuture future = bootstrap.bind(8080).sync();
System.out.println("服务器启动成功,监听端口: 8080");
// 等待服务器 Socket 关闭
future.channel().closeFuture().sync();
} finally {
// 优雅关闭线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
5.3 完整的客户端代码
java
public class NettyClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientHandler());
}
});
// 连接服务器
ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
System.out.println("连接服务器成功!");
// 发送消息
future.channel().writeAndFlush("Netty");
// 等待连接关闭
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
static class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("收到响应: " + msg);
}
}
}
六、性能调优与最佳实践
6.1 线程数配置建议
| 组件 | 推荐配置 | 说明 |
|---|---|---|
| Boss Group | 1 | Accept 事件处理快,无需多线程 |
| Worker Group | CPU 核心数 * 2 | I/O 密集型,适当增加线程数 |
| 业务 Handler 线程池 | 根据业务类型 | CPU 密集型:核心数 + 1 I/O 密集型:核心数 * 2 |
6.2 常见性能问题与优化
问题 1:EventLoop 线程阻塞
现象:吞吐量下降,响应变慢
原因:在 Handler 中执行耗时操作(数据库查询、文件 I/O)
解决方案:
java
// ❌ 错误做法:阻塞 EventLoop 线程
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String result = heavyDatabaseQuery(); // 阻塞操作
ctx.writeAndFlush(result);
}
// ✅ 正确做法:异步处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
CompletableFuture.supplyAsync(() -> heavyDatabaseQuery(), businessExecutor)
.thenAcceptAsync(result -> ctx.writeAndFlush(result), ctx.channel().eventLoop());
}
问题 2:内存泄漏
现象:堆外内存持续增长
原因:ByteBuf 未释放
解决方案:
java
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
try {
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
// 处理数据
} finally {
ReferenceCountUtil.release(buf); // 确保释放
}
}
6.3 参数调优对比
| 参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| SO_BACKLOG | 1024 | 8192+ | 高并发场景适当增大 |
| SO_RCVBUF | 87380 | 65536+ | 根据消息大小调整 |
| SO_SNDBUF | 16384 | 65536+ | 根据消息大小调整 |
| CONNECT_TIMEOUT_MILLIS | 30000 | 5000 | 根据业务需求调整 |
七、总结
Netty 的 Reactor 模型通过 Boss、Worker 线程的职责分离和 EventLoop 的事件循环机制,实现了高性能的异步 I/O 处理。核心要点:
- 主从 Reactor 模型:Boss 负责连接,Worker 负责 I/O
- EventLoop 串行化:无锁化设计,避免多线程竞争
- 任务队列机制:I/O 事件与业务任务分离,提高吞吐量
- 灵活的线程模型:可根据业务特点调整线程数量
理解 Netty 的 Reactor 模型,不仅能帮助我们更好地使用 Netty,也为学习其他高性能框架(如 Redis、Nginx)打下了坚实基础。
参考资料
- Netty 官方文档 - Thread Model
- Netty 4.1.x 源码
- Doug Lea. "Scalable IO in Java"
-
深入理解 Netty Reactor 模型\](https://learn.lianglianglee.com/专栏/Netty 核心原理剖析与 RPC 实践-完/02 纵览全局:把握 Netty 整体架构脉络.md)
标签:Netty、Reactor、EventLoop、线程模型、源码解析