Netty架构与组成
1. Netty架构概述
1.1 背景与设计哲学
在高并发网络编程场景中,Java原生的 BIO(Blocking I/O) 和 NIO(New I/O) 存在一定的开发痛点:
BIO:线程模型是“一连接一线程”,当连接数过多时,线程资源被迅速耗尽,难以支撑大规模并发。
NIO:虽然引入了
Selector
实现多路复用,但API复杂、易出错,开发门槛较高。
为解决这些问题,Netty应运而生。它的设计哲学是:
事件驱动(Event-Driven):一切网络I/O操作(连接、读写、异常)都被抽象为事件,通过事件传播来触发处理逻辑。
异步非阻塞(Asynchronous Non-blocking):网络操作不会阻塞调用线程,而是通过回调机制处理结果。
高性能(High Performance):利用零拷贝、内存池化、底层epoll/kqueue支持,最大化发挥操作系统能力。
易用性(User-Friendly API):通过统一的抽象(Channel、Pipeline、Handler等),大大简化了开发难度。
1.2 Netty的整体架构
从宏观层面,Netty的架构可以分为以下几个核心模块:
Channel(通道)
网络连接的抽象。
负责I/O操作(读、写、绑定、连接)。
每个连接对应一个
Channel
实例。
EventLoop / EventLoopGroup(事件循环/事件循环组)
基于Reactor模型。
EventLoop
绑定一个线程,负责轮询事件和执行任务。EventLoopGroup
是多个EventLoop
的集合,用于Boss(接收连接)和Worker(处理读写)。
ChannelPipeline(管道)
事件传播链。
内部是一条双向链表,包含多个
ChannelHandler
。支持职责链模式,Inbound事件和Outbound事件双向流动。
ChannelHandler(处理器)
业务逻辑的承载单元。
分为Inbound(处理读事件)和Outbound(处理写事件)。
用户可以自定义Handler,实现解码、编码、业务逻辑、异常处理。
ByteBuf(缓冲区)
Netty的数据容器,替代Java NIO的
ByteBuffer
。提供更灵活的读写指针机制。
支持池化和零拷贝,提升内存使用效率。
Bootstrap / ServerBootstrap(启动器)
封装客户端和服务端启动流程。
提供链式API,简化配置(绑定线程池、通道类型、Handler等)。
1.3 Netty的工作流程(整体视角)
以服务端为例,Netty的整体处理流程如下:
服务端启动:
通过ServerBootstrap
配置BossGroup
和WorkerGroup
,指定通道类型(如NioServerSocketChannel
)、Pipeline初始化逻辑。建立连接:
Boss线程接收客户端连接请求,并注册到某个Worker线程对应的EventLoop
。事件处理:
Worker线程通过
Selector
轮询I/O事件。I/O事件被包装成事件对象,沿着
ChannelPipeline
传播。触发对应的
ChannelHandler
方法,例如channelRead()
。
数据读写:
数据读取:数据从
Channel
读取后,写入ByteBuf
,然后触发Inbound事件。数据写入:通过Outbound事件传递,最终由底层
Channel
写到Socket。
异常与关闭:
出现异常时会调用exceptionCaught()
方法,资源回收则由Netty的生命周期管理机制自动处理。
1.4 Netty的优势
跨平台:支持NIO、Epoll、KQueue等多种传输实现。
可扩展性:基于Handler链的设计,使得业务逻辑模块化。
高性能:零拷贝、内存池化、写合并、任务队列优化。
广泛应用:Dubbo、gRPC、RocketMQ、Elasticsearch、Spring Cloud Gateway 等均基于Netty。
1.5 服务端启动示例
下面我们先展示一个最简单的Netty服务端启动流程,后续章节会深入剖析其中的原理:
public class NettyServer {public static void main(String[] args) throws InterruptedException {// 1. BossGroup 处理连接事件,WorkerGroup 处理读写事件EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup) // 设置Boss和Worker.channel(NioServerSocketChannel.class) // 指定NIO通信.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {// 配置Pipelinech.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) {System.out.println("收到消息: " + msg);ctx.writeAndFlush("Echo: " + msg);}});}});// 绑定端口,启动服务ChannelFuture future = bootstrap.bind(8080).sync();future.channel().closeFuture().sync();} finally {// 优雅关闭线程池bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
这个例子演示了Netty的核心骨架:
ServerBootstrap
负责启动。BossGroup
和WorkerGroup
分工明确。Pipeline
中添加Handler,处理业务逻辑。
2. 核心组件详解
Netty 的高性能和灵活性,离不开几个核心组件的协作。这些组件相互解耦,却又紧密配合,构成了 Netty 的整体框架。
2.1 Channel —— 网络连接的抽象
2.1.1 定义与作用
Channel
表示一个到网络套接字(Socket)的连接,既可能是服务端监听的ServerSocketChannel
,也可能是客户端/服务端的SocketChannel
。它是 所有 I/O 操作的入口,包括:
连接:
connect()
绑定:
bind()
读写:
read() / write()
关闭:
close()
2.1.2 Channel 的层次结构
Channel
接口定义了通用操作。常见实现:
NioServerSocketChannel
:基于 NIO 的服务端 Channel。NioSocketChannel
:基于 NIO 的客户端 Channel。EpollSocketChannel
:基于 Linux epoll 的客户端 Channel。
源码(简化版):
public interface Channel extends AttributeMap, Comparable<Channel> {EventLoop eventLoop(); // 绑定的事件循环ChannelPipeline pipeline(); // 关联的PipelineChannelFuture write(Object msg); // 异步写ChannelFuture close(); // 异步关闭
}
2.1.3 使用示例
Channel channel = ...;
// 写数据
channel.writeAndFlush(Unpooled.copiedBuffer("Hello", CharsetUtil.UTF_8));
// 关闭连接
channel.close();
2.2 EventLoop 与 EventLoopGroup —— 线程与事件循环
2.2.1 设计理念
每个
EventLoop
绑定一个单独的线程,负责:I/O 事件轮询(基于 Selector)。
执行任务队列中的任务(定时任务、异步任务)。
EventLoopGroup
是EventLoop
的集合,常见于:BossGroup:负责接收连接。
WorkerGroup:负责处理 I/O 读写。
2.2.2 源码简析
NioEventLoopGroup
的构造逻辑:
public NioEventLoopGroup(int nThreads) {super(nThreads, Executors.defaultThreadFactory());
}
nThreads
:线程数(默认=CPU核心数*2)。每个线程都绑定一个
NioEventLoop
。
NioEventLoop
的核心循环:
for (;;) {int ready = selector.select();processSelectedKeys();runAllTasks(); // 处理任务队列
}
2.2.3 示例:BossGroup 与 WorkerGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
// Boss只处理accept事件,Worker处理read/write事件
2.3 ChannelPipeline —— 事件传播链
2.3.1 定义
ChannelPipeline
是事件在ChannelHandler
间传播的载体。内部是一个 双向链表,每个节点是一个
ChannelHandlerContext
。
2.3.2 Inbound 与 Outbound
Inbound 事件:数据读取、连接建立 —— 从头到尾传播。
Outbound 事件:写数据、关闭 —— 从尾到头传播。
2.3.3 源码简析
Pipeline 添加 Handler:
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new MyHandler());
数据流向:
入站:
HeadContext -> Decoder -> Handler
出站:
Handler -> Encoder -> HeadContext
2.4 ChannelHandler —— 业务逻辑处理器
2.4.1 分类
ChannelInboundHandler:处理入站事件(
channelRead
、channelActive
)。ChannelOutboundHandler:处理出站事件(
write
、flush
)。
2.4.2 示例:自定义 Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("收到消息: " + msg);ctx.writeAndFlush(msg); // 回写给客户端}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}
2.5 ByteBuf —— 高性能缓冲区
2.5.1 为什么不用 ByteBuffer?
Java NIO 的
ByteBuffer
缺陷:API 不友好(flip() / rewind() 容易出错)。
容量固定,扩容麻烦。
读写指针混用。
2.5.2 ByteBuf 的优势
独立读写指针:
readerIndex
和writerIndex
。池化机制:减少 GC 压力。
零拷贝支持:如
CompositeByteBuf
。
2.5.3 常见实现
HeapByteBuf
:基于 JVM 堆。DirectByteBuf
:基于直接内存,减少一次拷贝。PooledByteBuf
:池化版本。
2.5.4 示例
ByteBuf buffer = Unpooled.buffer(16); // 初始容量16字节
buffer.writeBytes("Netty".getBytes());
System.out.println("可读字节数: " + buffer.readableBytes());
String data = buffer.toString(CharsetUtil.UTF_8);
System.out.println("内容: " + data);
3. 线程模型与事件驱动机制
3.1 Java I/O 模型的演进背景
在理解 Netty 线程模型之前,先对比下 Java 提供的 I/O 模型:
BIO(Blocking I/O):
一连接一线程,连接数多时线程资源耗尽,性能瓶颈明显。NIO(Non-blocking I/O):
引入了Selector
多路复用,一个线程可同时监听多个连接,但编程复杂度高。Netty 的改进:
基于 Reactor 模式,对线程池和事件循环做了优化,简化了 API,同时提升了吞吐量。
3.2 Reactor 模式概述
Reactor 模式:核心思想是由少量线程统一监听所有 I/O 事件,并分发给事件处理器(Handler)。
Netty 采用的是 多 Reactor 主从模型:
BossGroup(主 Reactor):负责接收客户端连接(accept)。
WorkerGroup(从 Reactor):负责处理已建立连接的读写事件。
这样做的好处是 连接接入 与 业务处理 解耦,避免单点瓶颈。
3.3 Netty 线程模型的工作流程
以服务端为例,Netty 的线程模型流程如下:
启动阶段:
ServerBootstrap
初始化两个线程池:BossGroup 和 WorkerGroup。BossGroup 绑定端口,监听连接。
连接建立:
当有新连接到来,BossGroup 中的
NioEventLoop
通过Selector
监听到ACCEPT
事件。Boss 将该连接分配给 WorkerGroup 中的某个
NioEventLoop
。
读写事件处理:
WorkerGroup 中的线程负责该连接的 I/O 事件轮询(
READ
、WRITE
)。事件被分发到
ChannelPipeline
,由对应的Handler
处理。
任务执行:
除了 I/O 事件,
EventLoop
还能执行普通任务和定时任务(如业务逻辑耗时计算)。
3.4 EventLoop 事件循环机制
3.4.1 NioEventLoop 核心循环
源码简化版:
for (;;) {try {// 1. 轮询 I/O 事件int readyCount = selector.select();// 2. 处理选中的事件processSelectedKeys();// 3. 执行任务队列runAllTasks();} catch (Exception e) {handleLoopException(e);}
}
selector.select():阻塞监听 I/O 事件。
processSelectedKeys():处理 Channel 的可读/可写/连接事件。
runAllTasks():执行提交到该 EventLoop 的普通任务。
3.4.2 任务队列
Netty 将任务分为两类:
普通任务:通过
eventLoop.execute(Runnable)
提交。定时任务:通过
eventLoop.schedule(Runnable, delay, unit)
提交。
这种设计保证了 I/O 事件 与 普通任务 在同一线程执行,避免并发问题。
3.5 BossGroup 与 WorkerGroup 协作示例
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 一个线程监听accept
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 默认=CPU核数*2ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new EchoServerHandler());}});b.bind(8080).sync();
执行流程:
BossGroup 监听
ACCEPT
事件。新连接分配给 WorkerGroup 的某个 EventLoop。
WorkerGroup 的线程轮询该连接的读写事件。
3.6 单线程、单 Reactor 与多 Reactor 的对比
模型 | 特点 | 问题 | Netty 优化点 |
---|---|---|---|
单线程 | 一个线程处理所有事件 | 容易阻塞,性能差 | —— |
单 Reactor 多线程 | 一个 Reactor + 线程池 | Reactor 容易成为瓶颈 | —— |
多 Reactor 主从模型 | 主 Reactor 负责连接,从 Reactor 处理读写 | 负载均衡,高并发性能好 | Netty 采用 |
Netty 采用 主从 Reactor 模型,既保证了高吞吐量,又能充分利用多核 CPU。
3.7 示例:向 EventLoop 提交任务
Channel channel = ...;
EventLoop eventLoop = channel.eventLoop();// 提交普通任务
eventLoop.execute(() -> {System.out.println("任务1在线程: " + Thread.currentThread().getName());
});// 提交定时任务
eventLoop.schedule(() -> {System.out.println("定时任务在线程: " + Thread.currentThread().getName());
}, 5, TimeUnit.SECONDS);
输出中你会发现,无论普通任务还是定时任务,都运行在 同一个 EventLoop 线程,避免了并发问题。
3.8 小结
本章我们学习了 Netty 的线程模型:
BossGroup 接收连接,WorkerGroup 处理读写。
每个
EventLoop
绑定一个线程,负责事件轮询和任务执行。采用 多 Reactor 主从模型,既解耦 accept 与 I/O,又发挥多核性能。
事件循环机制 确保 I/O 事件和任务执行在同一线程,避免锁开销。
4. 源码实现解析(关键类与方法分析)
4.1 NioEventLoopGroup —— 线程池的实现
4.1.1 构造方法
NioEventLoopGroup
继承自 MultithreadEventLoopGroup
,其核心逻辑是创建若干个 NioEventLoop
作为线程池成员。
public final class NioEventLoopGroup extends MultithreadEventLoopGroup {public NioEventLoopGroup(int nThreads) {super(nThreads, Executors.defaultThreadFactory());}
}
nThreads
:线程数,默认值 = CPU核心数 * 2。每个
NioEventLoop
都会绑定一个独立线程,并持有一个Selector
。
4.1.2 NioEventLoop 的启动逻辑
@Override
protected void run() {for (;;) {try {// 1. 轮询 I/O 事件int readyCount = selector.select();// 2. 处理选中的事件processSelectedKeys();// 3. 执行任务队列runAllTasks();} catch (Throwable t) {handleLoopException(t);}}
}
selector.select()
:监听所有注册的 Channel I/O 事件。processSelectedKeys()
:遍历 SelectionKey,调用 Channel 对应的事件处理器。runAllTasks()
:执行异步提交的任务(Runnable/定时任务)。
4.2 Channel 注册流程
当一个新的连接到来时,Netty 会将 Channel
注册到某个 EventLoop
。
4.2.1 Boss 接收连接
NioServerSocketChannel
监听 ACCEPT
事件:
protected int doReadMessages(List<Object> buf) throws Exception {SocketChannel ch = javaChannel().accept();if (ch != null) {buf.add(new NioSocketChannel(this, ch));return 1;}return 0;
}
accept()
系统调用返回一个新连接。封装为
NioSocketChannel
。
4.2.2 注册到 EventLoop
@Override
protected void doRegister() throws Exception {boolean selected = false;for (;;) {try {selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);return;} catch (CancelledKeyException e) {if (!selected) {eventLoop().selectNow();selected = true;} else {throw e;}}}
}
每个 Channel 都会被注册到 某个 EventLoop 的 Selector。
从此该 EventLoop 线程负责该 Channel 的所有 I/O。
4.3 ChannelPipeline 的事件传播机制
4.3.1 结构
内部是一个 双向链表:
HeadContext <-> Handler1 <-> Handler2 <-> ... <-> TailContext
4.3.2 Inbound 传播
public ChannelPipeline fireChannelRead(Object msg) {AbstractChannelHandlerContext.invokeChannelRead(head, msg);return this;
}static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {next.invokeChannelRead(msg);
}
fireChannelRead()
从 HeadContext 开始。顺着链表调用下一个 Handler 的
channelRead()
。
4.3.3 Outbound 传播
public ChannelFuture write(Object msg) {return tail.write(msg);
}public ChannelFuture write(Object msg, ChannelPromise promise) {AbstractChannelHandlerContext next = findContextOutbound();next.invokeWrite(msg, promise);
}
写事件从 TailContext 逆序向前传播。
最终到达 HeadContext,由底层 Channel 执行写操作。
4.4 ByteBuf 的内存管理
4.4.1 基本结构
ByteBuf
的三个关键属性:
readerIndex
:读指针。writerIndex
:写指针。capacity
:容量。
源码片段:
public abstract class AbstractByteBuf extends ByteBuf {private int readerIndex;private int writerIndex;private int capacity;
}
4.4.2 写操作
@Override
public ByteBuf writeBytes(byte[] src) {ensureWritable(src.length);setBytes(writerIndex, src);writerIndex += src.length;return this;
}
检查是否有足够空间。
将数据写入数组或直接内存。
移动
writerIndex
。
4.4.3 读操作
@Override
public byte readByte() {checkReadableBytes(1);return _getByte(readerIndex++);
}
确保可读字节数足够。
返回字节并移动
readerIndex
。
4.5 示例:数据流全链路
客户端写数据 → 调用
channel.writeAndFlush(msg)
。Outbound 传播 → Handler 链逐步处理,最终到达
HeadContext
。底层写入 Socket → NIO Channel 执行
write()
。服务端收到数据 → 触发
channelRead()
,数据沿 Inbound 链传播。Handler 处理数据 → 应用层逻辑执行。