Netty源码分析---Reactor线程模型深度解析(一)

Reactor 线程模型深度解析

一、什么是 Reactor 模式?

1.1 Reactor 模式的起源

Reactor 模式是一种事件驱动的设计模式,最早由 Douglas C. Schmidt 在 1995 年提出,用于处理并发的服务请求。

核心思想

  • 使用单个线程监听多个 I/O 事件源
  • 当事件发生时,分发给对应的处理器(Handler)
  • 避免为每个连接创建一个线程(传统的 Thread-Per-Connection 模型)

1.2 传统的 BIO 模型(Thread-Per-Connection)

在讲 Reactor 之前,我们先看看传统的 BIO 模型是怎么工作的。

BIO 服务器代码

java 复制代码
public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动,等待连接...");
        
        while (true) {
            // 阻塞等待客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("新连接: " + socket.getRemoteSocketAddress());
            
            // 为每个连接创建一个新线程
            new Thread(() -> {
                try {
                    InputStream in = socket.getInputStream();
                    OutputStream out = socket.getOutputStream();
                    
                    byte[] buffer = new byte[1024];
                    int len;
                    
                    // 阻塞读取数据
                    while ((len = in.read(buffer)) != -1) {
                        System.out.println("收到数据: " + new String(buffer, 0, len));
                        // 回写数据
                        out.write(buffer, 0, len);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();  // 启动新线程
        }
    }
}

BIO 模型的工作流程

复制代码
客户端1连接 → 创建线程1 → 线程1阻塞读取
客户端2连接 → 创建线程2 → 线程2阻塞读取
客户端3连接 → 创建线程3 → 线程3阻塞读取
...
客户端N连接 → 创建线程N → 线程N阻塞读取

结果:N 个连接 = N 个线程

BIO 模型的问题

问题 说明
线程开销大 每个线程占用 1MB 栈内存,1000 个连接 = 1GB 内存
上下文切换频繁 线程越多,CPU 在线程间切换的开销越大
线程阻塞 大部分时间线程都在等待 I/O,CPU 利用率低
扩展性差 无法支持大量并发连接(C10K 问题)

C10K 问题:如何在单台服务器上同时处理 10,000 个并发连接?BIO 模型无法解决。


二、Reactor 模式的演进

Reactor 模式有三种经典的实现方式,我们逐一分析。

2.1 单 Reactor 单线程模型

这是最简单的 Reactor 模型,所有操作都在一个线程中完成。

架构图

复制代码
┌─────────────────────────────────────────────────────┐
│                   单线程                              │
│  ┌──────────┐                                       │
│  │ Reactor  │  (Selector)                           │
│  └────┬─────┘                                       │
│       │                                              │
│       ├─ Accept  → Acceptor  → 建立连接              │
│       ├─ Read    → Handler1  → 读取数据              │
│       ├─ Write   → Handler2  → 写入数据              │
│       └─ ...                                         │
└─────────────────────────────────────────────────────┘

代码实现

java 复制代码
public class SingleReactorSingleThread {
    public static void main(String[] args) throws IOException {
        // 创建 Selector(Reactor)
        Selector selector = Selector.open();
        
        // 创建 ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(8080));
        
        // 注册 Accept 事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("单 Reactor 单线程服务器启动...");
        
        // 事件循环(单线程)
        while (true) {
            // 阻塞等待事件
            selector.select();
            
            // 处理所有就绪的事件
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                
                try {
                    // 处理 Accept 事件
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_READ);
                        System.out.println("接受连接: " + client.getRemoteAddress());
                    }
                    // 处理 Read 事件
                    else if (key.isReadable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        
                        int len = client.read(buffer);
                        if (len > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            System.out.println("收到数据: " + new String(data));
                            
                            // 注册 Write 事件
                            key.interestOps(SelectionKey.OP_WRITE);
                            key.attach(data);  // 将数据附加到 key
                        } else if (len == -1) {
                            // 客户端关闭连接
                            key.cancel();
                            client.close();
                        }
                    }
                    // 处理 Write 事件
                    else if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        byte[] data = (byte[]) key.attachment();
                        
                        ByteBuffer buffer = ByteBuffer.wrap(data);
                        client.write(buffer);
                        
                        // 写完后,重新注册 Read 事件
                        key.interestOps(SelectionKey.OP_READ);
                        key.attach(null);
                    }
                } catch (IOException e) {
                    key.cancel();
                    key.channel().close();
                }
            }
        }
    }
}

执行流程

复制代码
1. Selector 阻塞等待事件
   ↓
2. 客户端1连接 → Accept 事件 → Acceptor 处理 → 注册 Read 事件
   ↓
3. 客户端1发送数据 → Read 事件 → Handler 读取数据 → 业务处理
   ↓
4. 处理完成 → Write 事件 → Handler 写回数据
   ↓
5. 回到步骤 1

所有操作都在一个线程中顺序执行

优点

  • 简单,易于实现
  • 没有多线程竞争,不需要加锁
  • 一个线程可以处理多个连接(解决了 BIO 的线程开销问题)

缺点

  • 无法利用多核 CPU
  • 如果某个 Handler 处理时间过长,会阻塞其他所有连接
  • 性能瓶颈明显,无法支持高并发

适用场景

  • 连接数少(< 100)
  • 业务处理非常快(< 1ms)
  • 单核 CPU

2.2 单 Reactor 多线程模型

为了解决单线程模型的性能瓶颈,引入了线程池来处理业务逻辑。

架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      主线程                                   │
│  ┌──────────┐                                                │
│  │ Reactor  │  (Selector)                                    │
│  └────┬─────┘                                                │
│       │                                                       │
│       ├─ Accept  → Acceptor  → 建立连接                       │
│       ├─ Read    → 读取数据  → 提交到线程池                   │
│       └─ Write   → 写入数据                                   │
└───────┼───────────────────────────────────────────────────────┘
        │
        ↓ 提交任务
┌─────────────────────────────────────────────────────────────┐
│                    线程池                                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                  │
│  │ Thread 1 │  │ Thread 2 │  │ Thread 3 │  ...             │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                  │
│       │             │             │                          │
│       ↓             ↓             ↓                          │
│   Handler1      Handler2      Handler3                      │
│   (业务处理)    (业务处理)    (业务处理)                      │
└─────────────────────────────────────────────────────────────┘

代码实现

java 复制代码
public class SingleReactorMultiThread {
    // 创建线程池
    private static final ExecutorService threadPool = 
        Executors.newFixedThreadPool(10);
    
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(8080));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("单 Reactor 多线程服务器启动...");
        
        while (true) {
            selector.select();
            
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                
                try {
                    if (key.isAcceptable()) {
                        // Accept 在主线程处理
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_READ);
                        System.out.println("接受连接: " + client.getRemoteAddress());
                    }
                    else if (key.isReadable()) {
                        // Read 在主线程读取数据
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        
                        int len = client.read(buffer);
                        if (len > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            
                            // 关键:将业务处理提交到线程池
                            threadPool.submit(() -> {
                                // 业务处理(可能很耗时)
                                String result = processData(new String(data));
                                
                                // 处理完成后,需要写回数据
                                // 这里有个问题:如何通知主线程写数据?
                                // 方案1:使用队列
                                // 方案2:使用 Selector.wakeup()
                                try {
                                    ByteBuffer writeBuffer = ByteBuffer.wrap(result.getBytes());
                                    client.write(writeBuffer);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            });
                        } else if (len == -1) {
                            key.cancel();
                            client.close();
                        }
                    }
                } catch (IOException e) {
                    key.cancel();
                    key.channel().close();
                }
            }
        }
    }
    
    private static String processData(String data) {
        // 模拟耗时的业务处理
        try {
            Thread.sleep(100);  // 假设业务处理需要 100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "处理结果: " + data;
    }
}

执行流程

复制代码
1. 主线程 Selector 等待事件
   ↓
2. 客户端连接 → Accept 事件 → 主线程处理 → 注册 Read 事件
   ↓
3. 客户端发送数据 → Read 事件 → 主线程读取数据
   ↓
4. 主线程将数据提交到线程池
   ↓
5. 工作线程处理业务逻辑(耗时操作)
   ↓
6. 工作线程写回数据(或通知主线程写)
   ↓
7. 回到步骤 1

主线程负责 I/O,工作线程负责业务处理

优点

  • 充分利用多核 CPU
  • 业务处理不会阻塞 I/O 操作
  • 性能比单线程模型好很多

缺点

  • 主线程仍然是单线程,高并发下可能成为瓶颈
  • Accept、Read、Write 都在主线程,压力集中
  • 多线程编程复杂度增加(需要处理线程间通信)

适用场景

  • 连接数中等(100 - 1000)
  • 业务处理比较耗时
  • 多核 CPU

2.3 主从 Reactor 多线程模型(Netty 使用的模型)

这是最成熟的 Reactor 模型,也是 Netty 采用的模型。

核心思想

  • 将 Reactor 分为两组:主 Reactor(Boss)和从 Reactor(Worker)
  • 主 Reactor 只负责接受连接(Accept)
  • 从 Reactor 负责处理 I/O 读写(Read/Write)
  • 业务处理可以在从 Reactor 中完成,也可以提交到业务线程池

架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                  主 Reactor 线程池 (Boss)                     │
│  ┌──────────┐  ┌──────────┐                                 │
│  │ Reactor1 │  │ Reactor2 │  (通常只有 1 个)                │
│  └────┬─────┘  └────┬─────┘                                 │
│       │             │                                        │
│       ├─ Accept ────┴─ Accept                               │
│       │                                                      │
│       └─ 将新连接分配给从 Reactor                            │
└───────┼──────────────────────────────────────────────────────┘
        │
        ↓ 注册到从 Reactor
┌─────────────────────────────────────────────────────────────┐
│                  从 Reactor 线程池 (Worker)                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                  │
│  │ Reactor1 │  │ Reactor2 │  │ Reactor3 │  ...             │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                  │
│       │             │             │                          │
│       ├─ Read       ├─ Read       ├─ Read                   │
│       ├─ Decode     ├─ Decode     ├─ Decode                 │
│       ├─ Process    ├─ Process    ├─ Process                │
│       ├─ Encode     ├─ Encode     ├─ Encode                 │
│       └─ Write      └─ Write      └─ Write                  │
└─────────────────────────────────────────────────────────────┘

Netty 的实现

java 复制代码
public class MasterSlaveReactorServer {
    public static void main(String[] args) throws Exception {
        // 主 Reactor 线程池(Boss Group)
        // 通常只需要 1 个线程,因为 Accept 操作很快
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        
        // 从 Reactor 线程池(Worker Group)
        // 默认线程数 = CPU 核心数 * 2
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)  // 设置主从 Reactor
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            
                            // 解码器
                            pipeline.addLast(new StringDecoder());
                            // 编码器
                            pipeline.addLast(new StringEncoder());
                            // 业务处理器
                            pipeline.addLast(new ServerHandler());
                        }
                    });
            
            // 绑定端口
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("主从 Reactor 服务器启动,端口: 8080");
            
            // 等待服务器关闭
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    static class ServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String data = (String) msg;
            System.out.println("收到数据: " + data);
            
            // 业务处理
            String result = "处理结果: " + data;
            
            // 写回数据
            ctx.writeAndFlush(result);
        }
        
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }
}

执行流程

复制代码
1. 客户端发起连接
   ↓
2. Boss Reactor 接受连接(Accept)
   ↓
3. Boss Reactor 将新连接注册到某个 Worker Reactor
   ↓
4. 客户端发送数据
   ↓
5. Worker Reactor 检测到 Read 事件
   ↓
6. Worker Reactor 读取数据 → 解码 → 业务处理 → 编码 → 写回数据
   ↓
7. 回到步骤 4

Boss 专注于 Accept,Worker 专注于 Read/Write

优点

  • Boss 和 Worker 分离,职责明确
  • Boss 不会被 I/O 操作阻塞
  • Worker 可以有多个线程,充分利用多核 CPU
  • 每个连接绑定到一个 Worker 线程,无需加锁
  • 性能最好,可以支持百万级并发

缺点

  • 实现复杂度最高
  • 需要处理线程间的任务分配

适用场景

  • 高并发场景(> 1000 连接)
  • 需要充分利用多核 CPU
  • 对性能要求高的场景

三、三种模型的对比

3.1 性能对比

模型 线程数 并发能力 CPU 利用率 适用场景
单 Reactor 单线程 1 连接数 < 100
单 Reactor 多线程 1 + N (线程池) 连接数 100-1000
主从 Reactor 多线程 M (Boss) + N (Worker) 连接数 > 1000

3.2 处理流程对比

单 Reactor 单线程

复制代码
Accept → Read → Decode → Process → Encode → Write
└────────────── 全部在一个线程 ──────────────┘

单 Reactor 多线程

复制代码
Accept → Read → Decode ┐
└─ 主线程 ─────────────┘
                       ↓ 提交到线程池
                   Process (工作线程)
                       ↓
Encode → Write ←───────┘
└─ 主线程 ─┘

主从 Reactor 多线程

复制代码
Accept (Boss 线程)
   ↓
注册到 Worker
   ↓
Read → Decode → Process → Encode → Write
└────────── Worker 线程 ──────────────┘

相关推荐
_MyFavorite_4 小时前
JAVA重点基础、进阶知识及易错点总结(28)接口默认方法与静态方法
java·开发语言·windows
嵌入式小企鹅4 小时前
蓝牙学习系列(七):BLE GATT 数据模型详解
学习·蓝牙·ble·蓝牙协议栈·蓝牙开发·gatt
helx825 小时前
SpringBoot中自定义Starter
java·spring boot·后端
_MyFavorite_5 小时前
JAVA重点基础、进阶知识及易错点总结(31)设计模式基础(单例、工厂)
java·开发语言·设计模式
arvin_xiaoting5 小时前
OpenClaw学习总结_III_自动化系统_3:CronJobs详解
数据库·学习·自动化
少许极端5 小时前
算法奇妙屋(四十一)-贪心算法学习之路 8
学习·算法·贪心算法
ILYT NCTR5 小时前
SpringSecurity 实现token 认证
java
rleS IONS5 小时前
SpringBoot获取bean的几种方式
java·spring boot·后端
014-code6 小时前
Java SPI 实战:ServiceLoader 的正确打开方式(含类加载器坑)
java·开发语言
程序员榴莲6 小时前
Javase(七):继承
java