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>() {
@Override
protected void initChannel(SocketChannel ch) {
// 配置Pipeline
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected 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(); // 关联的Pipeline
ChannelFuture 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 {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("收到消息: " + msg);
ctx.writeAndFlush(msg); // 回写给客户端
}
@Override
public 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
还能执行普通任务和定时任务(如业务逻辑耗时计算)。
- 除了 I/O 事件,
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核数*2
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected 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 处理数据 → 应用层逻辑执行。