Netty原理分析
文章目录
- Netty原理分析
-
- [1. 简介](#1. 简介)
- 2.Netty核心组件理解
- 3.关键技术点源码分析
-
- [线程模型 (Reactor模式)](#线程模型 (Reactor模式))
- 无锁串行化设计
- 4.最佳实践与注意事项
1. 简介
Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的协议服务器和客户端。它封装了 JDK 底层复杂的 NIO(Non-blocking IO)API,提供了一套更加易用、安全、高效的网络编程接口。
很多知名的开源项目(如 Dubbo、RocketMQ、Elasticsearch)的底层网络通信层都采用了 Netty,我自己做即时通讯、物联网平台、智能家居等相关的项目也频繁的用到了Netty。
2.Netty核心组件理解
-
Channel(通道)
Channel 可理解为一条TCP的连接的抽象抽象,代表了一个到实体(如硬件设备、文件、网络套接字)的开放连接。你可以把它理解为传入(Inbound)或传出(Outbound)数据的载体。
-
EventLoopGroup
用来管理EventLoop
-
EventLoop(事件循环)
EventLoop 负责处理 Channel 的所有 I/O 操作。一个 EventLoop 内部维护了一个线程,在其生命周期内,它会处理注册在其上的 Channel 的所有事件(如连接建立、IO数据读写)。
机制: 一个 EventLoop 可以管理多个 Channel,但一个 Channel 只会绑定到一个 EventLoop 上,它将在它的整个生命周期中都使用这个
EventLoop(以及相关联的 Thread)。从而保证了线程安全,更详细的见下面的:无锁串行化设计。
-
ChannelFuture(异步结果)
由于 Netty 的操作都是异步的,操作会立即返回,但不保证此时已经完成。ChannelFuture 用于在稍后的时间点获取操作的结果(成功或失败),或者注册监听器(Listener)在操作完成时触发回调。
-
ChannelHandler(通道处理器)
这是win开发者打交道最多的组件。它负责处理 I/O 事件或拦截 I/O 操作,比如:编码、解码操作、断包粘包处理、业务逻辑
- InboundHandler: 处理入站数据(如读取数据)。
- OutboundHandler: 处理出站数据(如发送数据)。
-
ChannelPipeline(流水线)
其实是一个channelHandler的责任链,当数据流经 Channel 时,会按照 Pipeline 中 Handler 的顺序依次进行处理。这就好比工厂的流水线,数据是原材料,Handler 是工序。
通过channelInitializer来吧channelHadler注册到channelPipleline
-
ByteBuf
双指针设计: ByteBuf 维护了 readerIndex(读指针)和 writerIndex(写指针)。写操作移动写指针,读操作移动读指针,无需像 JDK ByteBuffer 那样频繁调用 flip() 方法切换读写模式。
零拷贝(Zero Copy): Netty 提供了 CompositeByteBuf 等机制,允许将多个物理缓冲区在逻辑上组合成一个,或者直接进行文件传输,减少了数据在内存中的复制次数
核心组件关系图

ChannelPipeline和ChannelHandler
- 每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。这项关联是永久性的;
- 从头入站从尾出站:从左到右进行编号,那么第一个被入站事件看到的 ChannelHandler 将是1,而第一个被出站事件看到的 ChannelHandler 将是 5

3.关键技术点源码分析
线程模型 (Reactor模式)
- BossGroup(主线程池): 专门负责处理客户端的连接请求(Accept 事件)。一旦连接建立,它将连接注册到 WorkerGroup。
- WorkerGroup(工作线程池): 负责处理已建立连接的 I/O 读写事件(Read/Write 事件)以及非阻塞的业务逻辑。

无锁串行化设计
-
EventLoop的执行逻辑

-
SingleThreadEventLoop源码
java
//io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, boolean, io.netty.channel.ChannelPromise)
private void write(Object msg, boolean flush, ChannelPromise promise) {
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
final Object m = pipeline.touch(msg, next);
//1.将要在channel对应的EventLoop中执行任务,即从channel().eventLoop()获取
EventExecutor executor = next.executor();
//2.判断当前调用线程是否就是分配给到EventLLoop的那个线程
if (executor.inEventLoop()) {
//3.如果线程是EventLoop对应的线程,则直接执行任务
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
//4.如果线程不是EventLoop对应的线程,包装为任务放入队列以便下一次执行
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
// See https://github.com/netty/netty/issues/8343.
task.cancel();
}
}
}
private static boolean safeExecute(EventExecutor executor, Runnable runnable,
ChannelPromise promise, Object msg, boolean lazy) {
try {
executor.execute(runnable);
return true;
} catch (Throwable cause) {
return false;
}
}
//io.netty.util.concurrent.SingleThreadEventExecutor#inEventLoop
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
//io.netty.util.concurrent.SingleThreadEventExecutor#execute(java.lang.Runnable)
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
//添加到任务队列
addTask(task);
if (!inEventLoop) {
//如果没有开线程则开启
startThread();
}
}
4.最佳实践与注意事项
-
避免阻塞 EventLoop:****严禁 在
ChannelHandler中执行耗时操作(如 DB 查询、HTTP 调用)-
正确做法:提交到业务线程池处理
ctx.executor().submit(() -> { // 异步业务逻辑 ctx.writeAndFlush(response); }); -
-
合理释放资源: 对于入站消息(ByteBuf),如果已经被读取且不需要传递给下一个 Handler,务必调用 ReferenceCountUtil.release() 释放内存,防止内存泄漏。
-
粘包与拆包: TCP 是流式协议,Netty 提供了多种解码器(如 LengthFieldBasedFrameDecoder、LineBasedFrameDecoder)来解决数据传输中的粘包和拆包问题,开发时应优先使用这些现成的组件
javapublic class TcpFrameDecoder extends LengthFieldBasedFrameDecoder { public TcpFrameDecoder() { super( 2048, // 最大帧长 3, // 长度字段偏移(跳过2字节帧头+1字节版本) 2, // 长度字段长度 -1, // 长度调整 0 // 初始跳过字节数 ); } }