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的架构可以分为以下几个核心模块:

  1. Channel(通道)

    • 网络连接的抽象。

    • 负责I/O操作(读、写、绑定、连接)。

    • 每个连接对应一个Channel实例。

  2. EventLoop / EventLoopGroup(事件循环/事件循环组)

    • 基于Reactor模型。

    • EventLoop绑定一个线程,负责轮询事件和执行任务。

    • EventLoopGroup是多个EventLoop的集合,用于Boss(接收连接)和Worker(处理读写)。

  3. ChannelPipeline(管道)

    • 事件传播链。

    • 内部是一条双向链表,包含多个ChannelHandler

    • 支持职责链模式,Inbound事件和Outbound事件双向流动。

  4. ChannelHandler(处理器)

    • 业务逻辑的承载单元。

    • 分为Inbound(处理读事件)和Outbound(处理写事件)。

    • 用户可以自定义Handler,实现解码、编码、业务逻辑、异常处理。

  5. ByteBuf(缓冲区)

    • Netty的数据容器,替代Java NIO的ByteBuffer

    • 提供更灵活的读写指针机制。

    • 支持池化和零拷贝,提升内存使用效率。

  6. Bootstrap / ServerBootstrap(启动器)

    • 封装客户端和服务端启动流程。

    • 提供链式API,简化配置(绑定线程池、通道类型、Handler等)。


1.3 Netty的工作流程(整体视角)

以服务端为例,Netty的整体处理流程如下:

  1. 服务端启动

    通过ServerBootstrap配置BossGroupWorkerGroup,指定通道类型(如NioServerSocketChannel)、Pipeline初始化逻辑。

  2. 建立连接

    Boss线程接收客户端连接请求,并注册到某个Worker线程对应的EventLoop

  3. 事件处理

    • Worker线程通过Selector轮询I/O事件。

    • I/O事件被包装成事件对象,沿着ChannelPipeline传播。

    • 触发对应的ChannelHandler方法,例如channelRead()

  4. 数据读写

    • 数据读取:数据从Channel读取后,写入ByteBuf,然后触发Inbound事件。

    • 数据写入:通过Outbound事件传递,最终由底层Channel写到Socket。

  5. 异常与关闭

    出现异常时会调用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负责启动。

  • BossGroupWorkerGroup分工明确。

  • 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)。

    • 执行任务队列中的任务(定时任务、异步任务)。

  • EventLoopGroupEventLoop 的集合,常见于:

    • 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 :处理入站事件(channelReadchannelActive)。

  • ChannelOutboundHandler :处理出站事件(writeflush)。

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 的优势

  • 独立读写指针readerIndexwriterIndex

  • 池化机制:减少 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 的线程模型流程如下:

  1. 启动阶段

    • ServerBootstrap 初始化两个线程池:BossGroup 和 WorkerGroup。

    • BossGroup 绑定端口,监听连接。

  2. 连接建立

    • 当有新连接到来,BossGroup 中的 NioEventLoop 通过 Selector 监听到 ACCEPT 事件。

    • Boss 将该连接分配给 WorkerGroup 中的某个 NioEventLoop

  3. 读写事件处理

    • WorkerGroup 中的线程负责该连接的 I/O 事件轮询(READWRITE)。

    • 事件被分发到 ChannelPipeline,由对应的 Handler 处理。

  4. 任务执行

    • 除了 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核数*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();

执行流程:

  1. BossGroup 监听 ACCEPT 事件。

  2. 新连接分配给 WorkerGroup 的某个 EventLoop。

  3. 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 示例:数据流全链路

  1. 客户端写数据 → 调用 channel.writeAndFlush(msg)

  2. Outbound 传播 → Handler 链逐步处理,最终到达 HeadContext

  3. 底层写入 Socket → NIO Channel 执行 write()

  4. 服务端收到数据 → 触发 channelRead(),数据沿 Inbound 链传播。

  5. Handler 处理数据 → 应用层逻辑执行。