Skip to content

Netty 全面知识体系

一、Netty 概述

Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。基于 Java NIO(Non-blocking I/O)实现,采用 Reactor 模式,提供高效、灵活的网络编程抽象。

核心特性

  • 高性能:零拷贝、内存池、自适应分配器
  • 高可维护性:清晰的 API 设计、完善的文档
  • 丰富的协议支持:HTTP、HTTP/2、HTTP/3、WebSocket、SSL/TLS
  • 跨平台传输层:NIO、OIO、Epoll(Linux)、KQueue(BSD/macOS)
  • 强大的生态:Spring Cloud、Dubbo、gRPC 等框架底层依赖

二、核心架构

2.1 Reactor 线程模型

Netty 采用经典的主从 Reactor 多线程模型

┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Boss      │────▶│   Accept Event   │────▶│   Worker        │
│ EventLoop   │     │   (注册连接)     │     │   EventLoop     │
│   Group     │     └──────────────────┘     │     Group       │
└─────────────┘                              │  ┌────────────┐ │
                                             │  │ IO Events  │ │
                                             │  │ (读写操作) │ │
                                             │  └────────────┘ │
                                             └─────────────────┘

BossGroup( Selector 组)

  • 负责接收客户端连接请求
  • 通常只需 1 个线程(单核即可处理 accept 操作)
  • 将已建立的连接注册到 WorkerGroup 的 Selector

WorkerGroup(IO 线程组)

  • 负责网络 IO 读写操作
  • 默认线程数 = CPU 核心数 * 2
  • 每个 Channel 绑定到一个 EventLoop,且生命周期内不变(线程安全)

2.2 EventLoop 核心机制

EventLoop 是 Netty 的心脏,关键特性:

java
// EventLoop 核心接口
public interface EventLoop extends Executor {
    boolean inEventLoop(Thread thread);           // 判断当前线程是否属于该 EventLoop
    Future<?> submit(Runnable task);              // 提交任务
    void shutdownGracefully();                    // 优雅关闭
    ChannelFuture register(Channel channel);      // 注册 Channel
}

重要特性:

  1. 一个 EventLoop 对应一个线程(NioEventLoop)
  2. Channel 与 EventLoop 一对一绑定,保证线程安全
  3. 运行队列 + Selector:既处理 IO 事件也处理定时任务/普通任务
  4. 无限循环while (!shutdown) 不断轮询 selector 和处理任务队列

2.3 Channel 通道

Channel 是 Netty 对网络连接的抽象,类似 JDK NIO 的 SocketChannel 但更强大:

java
public interface Channel {
    ChannelConfig config();                       // 获取配置
    boolean isRegistered();                       // 是否已注册到 EventLoop
    boolean isActive();                           // 是否处于活动状态
    ChannelMetadata metadata();                   // 元数据
    ChannelPipeline pipeline();                   // 获取责任链
    ChannelFuture connect(SocketAddress remoteAddress);  // 发起连接
    ChannelFuture bind(SocketAddress localAddress);      // 绑定端口
    ChannelFuture close();                        // 关闭通道
}

Channel 状态机:

  • NEWREGISTEREDACTIVEINACTIVECLOSED

2.4 ChannelPipeline & ChannelHandler

Pipeline(责任链)

Pipeline 是 Handler 的容器,形成单向链表结构

Head Handler → Business Handler 1 → Business Handler 2 → ... → Tail Handler

消息流向:

  • Inbound(入站):从 Head 向 Tail 传播(自左向右)
  • Outbound(出站):从 Tail 向 Head 传播(自右向左)

ChannelHandler 分类

类型适配器主要方法说明
InboundChannelInboundHandlerAdapterchannelRead, channelActive, exceptionCaught处理入站事件
OutboundChannelOutboundHandlerAdapterwrite, read, flush, connect处理出站操作
DuplexChannelDuplexHandler两者都有同时处理双向

核心生命周期方法:

java
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {}     // 添加时调用
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {}   // 移除时调用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {}  // 异常捕获

Inbound 关键方法:

java
void channelRegistered(ChannelHandlerContext ctx);      // Channel 注册到 EventLoop
void channelUnregistered(ChannelHandlerContext ctx);    // Channel 从 EventLoop 注销
void channelActive(ChannelHandlerContext ctx);          // 连接建立成功
void channelInactive(ChannelHandlerContext ctx);        // 连接断开
void channelRead(ChannelHandlerContext ctx, Object msg);// 读取到数据
void channelReadComplete(ChannelHandlerContext ctx);    // 读取完成
void userEventTriggered(ChannelHandlerContext ctx, Object evt); // 用户自定义事件

2.5 Bootstrap 引导类

ServerBootstrap(服务端)

java
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap bootstrap = new ServerBootstrap()
    .group(bossGroup, workerGroup)                          // 设置线程组
    .channel(NioServerSocketChannel.class)                  // 服务端 Channel 类型
    .option(ChannelOption.SO_BACKLOG, 128)                  // 服务端选项
    .childOption(ChannelOption.SO_KEEPALIVE, true)          // 子 Channel 选项
    .childHandler(new ChannelInitializer<SocketChannel>() { // 子 Channel 初始化器
        @Override
        protected void initChannel(SocketChannel ch) {
            ch.pipeline().addLast(new BusinessHandler());
        }
    });

ChannelFuture future = bootstrap.bind(8080).sync();
future.channel().closeFuture().sync();  // 等待关闭

Bootstrap(客户端)

java
EventLoopGroup group = new NioEventLoopGroup();

Bootstrap bootstrap = new Bootstrap()
    .group(group)
    .channel(NioSocketChannel.class)
    .option(ChannelOption.TCP_NODELAY, true)
    .handler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) {
            ch.pipeline().addLast(new ClientHandler());
        }
    });

ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();

三、ByteBuf 缓冲区

3.1 ByteBuf 核心概念

ByteBuf 是 Netty 对 JDK NIO ByteBuffer 的增强,解决了很多痛点:

特性JDK ByteBufferNetty ByteBuf
读/写索引共用 position,需要 flip()独立 readerIndex/writerIndex
边界检查严格,越界抛异常可选择性跳过检查
堆外内存需手动管理统一管理,自动释放
复合缓冲不支持CompositeByteBuf
零拷贝不支持slice/duplicate/wrap

3.2 内存管理

引用计数机制

java
ByteBuf buf = Unpooled.buffer(1024);
System.out.println(buf.refCnt());  // 输出 1,初始引用计数为 1

buf.retained();    // 引用计数 +1
buf.release();     // 引用计数 -1,归零时释放内存

重要规则:

  • 新创建的 ByteBuf 引用计数为 1
  • 必须手动 release(),否则会导致内存泄漏
  • pipeline 中传递的消息会自动释放,但业务处理后需自行管理
  • CompositeByteBuf 的子 buffer 不会自动释放

堆内 vs 堆外

类型分配位置GC 影响性能适用场景
Heap BufferJVM 堆内受 GC 影响较低小对象、频繁访问
Direct BufferJVM 堆外不受 GC 影响网络 IO、大文件
java
ByteBufAllocator allocator = ctx.alloc();
ByteBuf heapBuf = allocator.heapBuffer(1024);      // 堆内缓冲
ByteBuf directBuf = allocator.directBuffer(1024);  // 堆外缓冲
ByteBuf autoBuf = allocator.buffer(1024);          // 自动选择

3.3 内存池(PooledByteBufAllocator)

Netty 4.x 使用内存池减少内存分配和 GC 压力:

java
// 默认配置
PooledByteBufAllocator.DEFAULT;

// 自定义配置
PooledByteBufAllocator allocator = new PooledByteBufAllocator(
    true,                                    // preferDirect: 优先使用堆外内存
    4,                                       // numHeapArena: 堆内内存区域数
    4,                                       // numDirectArena: 堆外内存区域数
    PageOrder(11),                           // chunkSize = 2^11 = 2KB
    PageOrder(9),                            // pageSize = 2^9 = 512B
    PageOrder(4),                            // smallCacheSize
    PageOrder(4),                            // normalCacheSize
    true,                                    // useCacheForAllThreads
    64,                                      // tinyCacheSize
    1                                        // maxCachedBufferCapacity
);

内存层级结构:

Chunk (16MB) → SubPage/PageSize (16KB/8KB/4KB/2KB/1KB/512B) → PoolChunk → PoolArena

3.4 零拷贝技术

(1) CompositeByteBuf(聚合缓冲区)

java
CompositeByteBuf composite = Unpooled.compositeBuffer();
composite.addComponent(true, headerBuf);
composite.addComponent(true, contentBuf);
composite.addComponent(true, footerBuf);
// 多个 buffer 逻辑上合并为一个,物理上不复制

(2) slice/duplicate(视图缓冲)

java
ByteBuf original = Unpooled.buffer(1024);
original.writeBytes(...);

ByteBuf slice = original.slice();           // 共享内存,独立索引
ByteBuf duplicate = original.duplicate();   // 共享内存和索引

(3) wrap(包装已有数组)

java
byte[] array = new byte[1024];
ByteBuf wrapped = Unpooled.wrappedBuffer(array);  // 不复制数据

(4) FileRegion & sendfile

java
// 利用操作系统内核零拷贝
RandomAccessFile file = new RandomAccessFile("video.mp4", "r");
FileRegion region = new DefaultFileRegion(file.getChannel(), 0, file.length());
ctx.writeAndFlush(region, ctx.newPromise());

四、编解码框架(Codec)

4.1 编解码器基类

ByteToMessageDecoder(字节转对象)

解决 TCP 粘包/拆包问题:

java
public class TimeDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        // in 是累积的缓冲区,可能包含多条消息
        if (in.readableBytes() < 4) {
            return;  // 数据不足,等待后续数据
        }
        int value = in.readInt();
        out.add(value);  // 解码后的消息加入输出列表
    }
}

关键方法:

  • decode(): 核心解码逻辑,被反复调用直到无法继续解码
  • decodeRemovalTry(): 处理剩余数据
  • cumulate(): 累积数据

MessageToByteEncoder(对象转字节)

java
public class TimeEncoder extends MessageToByteEncoder<Integer> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
        out.writeInt(msg);
    }
}

4.2 帧处理器(解决粘包拆包)

LengthFieldBasedFrameDecoder(长度字段)

最常用,适用于自定义协议:

java
new LengthFieldBasedFrameDecoder(
    1024 * 1024,   // maxFrameLength: 最大帧长度
    0,             // lengthFieldOffset: 长度字段偏移量
    4,             // lengthFieldLength: 长度字段长度
    0,             // lengthAdjustment: 长度修正值
    0              // initialBytesToStrip: 是否跳过长度字段
);

协议示例:[4 字节长度][实际数据]

DelimiterBasedFrameDecoder(分隔符)

java
// 以\r\n作为分隔符
new DelimiterBasedFrameDecoder(
    1024 * 1024,
    Unpooled.copiedBuffer("\r\n", CharsetUtil.UTF_8)
);

LineBasedFrameDecoder(行分割)

java
// 按行分割,自动处理 \n 或 \r\n
new LineBasedFrameDecoder(1024 * 1024);

FixedLengthFrameDecoder(固定长度)

java
// 每条消息固定 12 字节
new FixedLengthFrameDecoder(12);

4.3 内置编解码器

编解码器功能
StringEncoder/StringDecoder字符串与字节转换
ByteArrayEncoder/Decoder字节数组转换
Base64Encoder/Base64DecoderBase64 编码
JacksonJsonEncoder/JacksonJsonDecoderJSON 序列化
ProtobufEncoder/ProtobufDecoderProtocol Buffers
MarshallingEncoder/MarshallingDecoderJava 序列化

五、常用 Handler

5.1 IdleStateHandler(空闲检测)

java
new IdleStateHandler(
    60,  // readIdleTime: 读空闲时间(秒)
    30,  // writeIdleTime: 写空闲时间(秒)
    0,   // allIdleTime: 全部空闲时间(秒)
    TimeUnit.SECONDS
);

// 配合使用
public class IdleHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                ctx.close();  // 读空闲时关闭连接
            }
        }
    }
}

5.2 ReadTimeoutHandler / WriteTimeoutHandler

java
// 读超时自动关闭连接
new ReadTimeoutHandler(30, TimeUnit.SECONDS);

// 写超时自动关闭连接
new WriteTimeoutHandler(30, TimeUnit.SECONDS);

5.3 DecoderExceptionEncoder(解码异常处理)

java
// 当解码失败时,将异常编码后发送给客户端
ch.pipeline().addLast(new DecoderExceptionEncoder());

5.4 LoggingHandler(日志记录)

java
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));

5.5 SSL/TLS 支持

java
// 服务端 SSL 配置
SslContext sslCtx = SslContextBuilder.forServer(
    certChainFile, 
    privateKeyFile
).sslProvider(SslProvider.OPENSSL).build();

ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));

六、ChannelOption 配置

6.1 常用 TCP 选项

Option说明推荐值
SO_REUSEADDR允许复用本地地址true
TCP_NODELAY禁用 Nagle 算法true(低延迟场景)
SO_KEEPALIVETCP 保活true
SO_BACKLOG监听队列长度128/511/1024
SO_RCVBUF接收缓冲区大小根据场景调整
SO_SNDBUF发送缓冲区大小根据场景调整
IP_TOSType of Service默认 0

6.2 Netty 特有选项

Option说明
MAX_MESSAGES_PER_READ每次 selector 轮询最大读取消息数
AUTO_READ自动读取数据(默认 true)
WRITE_SPIN_COUNT单次 write 循环次数
ALLOCATOR内存分配器
MESSAGE_SIZE_ESTIMATOR消息大小估算器

6.3 水位线(Flow Control)

java
ch.config().setWriteBufferWaterMark(
    new WriteBufferWaterMark(32 * 1024, 64 * 1024)  // 低水位 32KB,高水位 64KB
);

七、异步编程模型

7.1 ChannelFuture & Promise

java
// Future-Promise 模式
ChannelFuture future = bootstrap.bind(8080);

// 同步等待(阻塞)
future.sync();

// 异步回调
future.addListener((ChannelFutureListener) f -> {
    if (f.isSuccess()) {
        System.out.println("绑定成功");
    } else {
        f.cause().printStackTrace();
    }
});

// 链式操作
future.addListener(FutureListener.of(f -> {
    if (f.isSuccess()) {
        // 成功处理
    }
}));

7.2 Promise 操作

java
Promise<Void> promise = ctx.newPromise();

// 设置成功
promise.setSuccess();

// 设置失败
promise.setFailure(new Exception("error"));

// 取消
promise.tryCancel(false);

7.3 组合 Future(Netty 5+)

java
future.map(result -> result * 2)       // 映射结果
      .flatMap(result -> anotherFuture) // 扁平化
      .addListener(f -> { /* 处理 */ });

八、传输层适配

8.1 NIO(Java NIO)

java
NioEventLoopGroup group = new NioEventLoopGroup();
NioServerSocketChannel serverChannel;
NioSocketChannel clientChannel;

8.2 Epoll(Linux 专用,性能最优)

java
EpollEventLoopGroup group = new EpollEventLoopGroup();
EpollServerSocketChannel serverChannel;
EpollSocketChannel clientChannel;

8.3 KQueue(BSD/macOS)

java
KQueueEventLoopGroup group = new KQueueEventLoopGroup();
KQueueServerSocketChannel serverChannel;
KQueueSocketChannel clientChannel;

8.4 OIO(阻塞 IO)

java
OioEventLoopGroup group = new OioEventLoopGroup();
OioServerSocketChannel serverChannel;
OioSocketChannel clientChannel;

九、高级特性

9.1 HTTP/HTTP2/WebSocket

HTTP 编解码

java
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65536));  // 聚合 FullHttpRequest

WebSocket

java
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));

HTTP/2

java
ch.pipeline().addLast(new Http2MultiplexCodecBuilder()
    .frameLogger(new Http2FrameLogger())
    .frameListener(new Http2FrameListener() {...})
    .build());

9.2 流量控制

手动控制读取

java
ch.config().setAutoRead(false);  // 禁用自动读取

// 在合适时机手动读取
ctx.read();

可写性检测

java
if (!ch.isWritable()) {
    // 停止写入,等待可写事件
}

@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
    if (ctx.channel().isWritable()) {
        // 恢复写入
    }
}

9.3 代理支持

java
// SOCKS4/5 代理
ch.pipeline().addLast(new SocksInitiator(socksAddr));

// HTTP 代理
ch.pipeline().addLast(new HttpProxyHandler(httpProxyAddr));

9.4 线程亲和性(Thread Affinity)

java
// 将线程绑定到特定 CPU 核心,降低上下文切换开销
ThreadFactory factory = new AffinityThreadFactory(
    "worker", 
    AffinityStrategies.DIFFERENT_CORE
);
EventLoopGroup group = new NioEventLoopGroup(0, factory);

9.5 半关闭 Socket

java
ch.config().setOption(ChannelOption.ALLOW_HALF_CLOSURE, true);
ch.shutdownOutput();  // 只关闭输出流,仍可读取

十、性能优化

10.1 内存优化

  1. 使用堆外直接内存:减少 GC 压力
  2. 合理配置内存池:避免频繁分配
  3. 及时释放 ByteBuf:防止内存泄漏
  4. 使用 CompositeByteBuf:减少数据拷贝

10.2 线程优化

  1. BossGroup 单线程足够:accept 操作很快
  2. WorkerGroup 线程数 = CPU 核心数 * 2
  3. CPU 隔离:使用 isolcpus 参数隔离核心
  4. 线程亲和性:绑定线程到特定核心

10.3 IO 优化

  1. 零拷贝:充分利用 sendfile、CompositeByteBuf
  2. 批量读写:增大 SO_RCVBUF/SO_SNDBUF
  3. 禁用 Nagle 算法:TCP_NODELAY=true
  4. 调整 epoll 参数:EPOLLET 边缘触发模式

10.4 系统级优化

bash
# Linux 内核参数调优
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

十一、最佳实践

11.1 资源管理

java
try {
    bossGroup = new NioEventLoopGroup();
    workerGroup = new NioEventLoopGroup();
    bootstrap = new ServerBootstrap()...;
    ChannelFuture future = bootstrap.bind(port).sync();
    future.channel().closeFuture().sync();
} finally {
    // 必须优雅关闭
    bossGroup.shutdownGracefully().sync();
    workerGroup.shutdownGracefully().sync();
}

11.2 异常处理

java
// 在每个 Handler 中添加 exceptionCaught
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    cause.printStackTrace();
    ctx.close();  // 发生异常时关闭连接
}

11.3 ByteBuf 使用规范

java
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    try {
        // 业务处理
    } finally {
        buf.release();  // 务必释放!
    }
}

十二、面试八股文速记版

一句话总结

Netty 是基于 Reactor 模式和 Java NIO 的异步事件驱动网络框架,通过 EventLoop 处理 IO 事件、ChannelPipeline 实现责任链、ByteBuf 管理零拷贝内存,提供高性能网络编程能力。

核心知识点速记

主题关键点
Reactor 模型主从 Reactor 多线程,Boss 负责 accept,Worker 负责读写
EventLoop一个 EventLoop 对应一个线程,Channel 与 EventLoop 一对一绑定
ChannelPipeline责任链模式,Inbound 从左向右,Outbound 从右向左
ByteBuf独立读写索引、引用计数、堆内外内存、内存池
零拷贝CompositeByteBuf、slice、wrap、FileRegion/sendfile
粘包拆包LengthFieldBasedFrameDecoder、DelimiterBasedFrameDecoder
内存泄漏ByteBuf 必须手动 release,引用计数归零才释放
线程安全同一 Channel 的所有操作由同一个 EventLoop 线程处理
优雅关闭shutdownGracefully() 等待任务完成再关闭线程
性能优化堆外内存、内存池、零拷贝、CPU 亲和性、内核参数调优

高频面试题

  1. Netty 为什么比 BIO/NIO 快?

    • 零拷贝技术、内存池减少 GC、Reactor 模型高效、Epoll 优化
  2. EventLoop 为什么不阻塞?

    • 非阻塞 IO + 异步任务队列 + 快速失败机制
  3. 如何保证 Channel 线程安全?

    • Channel 与 EventLoop 一对一绑定,所有操作在同一线程执行
  4. ByteBuf 为什么要引用计数?

    • 精确控制内存释放时机,避免 GC 延迟和内存泄漏
  5. 如何解决 TCP 粘包拆包?

    • 固定长度、分隔符、长度字段(最常用)、行分割
  6. Netty 内存泄漏怎么排查?

    • 检查 ByteBuf 是否 release、使用 jmap 分析 dump、开启 leak detection
  7. BossGroup 为什么可以只有一个线程?

    • accept 操作极快,单线程足以处理大量连接请求

本文档基于 Netty 官方文档和社区最佳实践整理,版本参考 Netty 4.x/5.x

更新于:

note