【netty】基于主从Reactor多线程模型|如何解决粘包拆包问题|零拷贝
1、基于Selector的NIO
基础概念:
ServerSocketChannel:服务器监听通道
- 监听指定端口,接受连接请求
- 每个服务器通常只有一个
- 只关注OP_ACCEPT事件
SocketChannel:客户端通信通道
- 与特定客户端进行数据交换
- 服务器有多个(每个客户一个)
- 关注OP_READ和OP_WRITE事件
原生基于Selector的NIO代码:
public class CompleteNioServer {private Selector selector;private ServerSocketChannel serverChannel;private final int PORT = 8080;public static void main(String[] args) throws IOException {new CompleteNioServer().start();}public void start() throws IOException {// 初始化selector = Selector.open();serverChannel = ServerSocketChannel.open();serverChannel.configureBlocking(false);serverChannel.socket().bind(new InetSocketAddress(PORT));serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO服务器启动,端口: " + PORT);// 事件循环eventLoop();}private void eventLoop() {while (true) {try {// 等待事件selector.select();// 处理所有就绪事件Iterator<SelectionKey> keys = selector.selectedKeys().iterator();while (keys.hasNext()) {SelectionKey key = keys.next();keys.remove();if (!key.isValid()) continue;if (key.isAcceptable()) {acceptClient(key);} else if (key.isReadable()) {readData(key);}}} catch (IOException e) {e.printStackTrace();}}}private void acceptClient(SelectionKey key) throws IOException {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel client = server.accept();client.configureBlocking(false);client.register(selector, SelectionKey.OP_READ);System.out.println("客户端连接: " + client.getRemoteAddress());}private void readData(SelectionKey key) throws IOException {SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = client.read(buffer);if (bytesRead == -1) {client.close();key.cancel();System.out.println("客户端断开");return;}if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data).trim();System.out.println("收到: " + message);// 回显String response = "Echo: " + message + "\n";client.write(ByteBuffer.wrap(response.getBytes()));}}
}
由以上代码可以看到:
Selector同时注册在ServerSocketChannel和多个SocketChannel上,Selector同时监听连接、读、写事件。
具体selector监控原理:(高性能的体现)
// 应用程序调用select(),线程阻塞,不消耗CPU资源
selector.select();// 底层:
// - 应用程序线程进入休眠状态
// - 操作系统内核监控所有注册的fd
// - 当有事件时,内核唤醒应用程序线程
2、Reactor模型
以上基于Selector的原生NIO代码可以看出,使用了selector这个工具,但需要自己设计架构。
while (true) {selector.select();Set<SelectionKey> keys = selector.selectedKeys();for (SelectionKey key : keys) {if (key.isAcceptable()) {// 需要自己处理连接建立} else if (key.isReadable()) {// 需要自己处理数据读取} else if (key.isWritable()) {// 需要自己处理数据写入}}// 问题:所有事情都在一个线程中处理,性能瓶颈!
}
Reactor模型解决了"如何使用Selector"的问题:
它的思想就是:
“不要把所有事情都塞给一个Selector处理
要分工协作,各司其职”
阶段1:单线程Reactor(直接使用Selector)
// 一个人干所有事情
单线程:接受连接 + 读取数据 + 业务处理 + 发送响应↓
问题:业务处理阻塞了新连接接受
阶段2:多线程Reactor(Selector + 线程池)
// 主线程:接受连接 + IO读写
// 线程池:业务处理
主线程(Selector) → 检测IO事件 → 提交业务到线程池↓
问题:IO读写和业务处理分离,但连接建立仍在主线程
阶段3:主从Reactor(多Selector分工)
// Boss Reactor:专门接受连接
// Worker Reactor:专门处理IO
// 线程池:专门业务处理Boss Selector(连接建立) → Worker Selector(IO读写) → 线程池(业务处理)
3、Netty基于主从Reactor多线程模型
与原生NIO相比,Netty做了什么优化?
-
线程模型优化:原生NIO需要自己设计,Netty是基于主从Reactor模型的完整实现,不用手动基于Selector去实现所有细节,Netty提供了开箱即用的最佳实践。
-
内存管理优化:ByteBuf池化、零拷贝,减少GC压力
-
协议支持完善:内置各种编解码器,解决粘包拆包问题
-
异常处理健全:连接异常、超时等都有完善的处理机制
以以上优化点为思路,我们来依次学习netty相关的技术点:
1、基于主从Reactor多线程模型
Netty的主从Reactor模型与原生的NIO有本质区别,主要体现在Selector架构上:
原生NIO通常使用单Selector模型:
- 一个Selector监控所有Channel(ServerSocketChannel + 所有SocketChannel)
- 所有事件类型(ACCEPT、READ、WRITE)都由同一个Selector处理
- 单个线程成为性能瓶颈,无法充分利用多核CPU
Netty采用多Selector模型:
- Boss Group使用专用的Selector,只监听ServerSocketChannel的ACCEPT事件
- Worker Group使用多个Selector,每个Worker线程有自己的Selector
- 每个Worker Selector只管理一部分SocketChannel的READ/WRITE事件
- 真正实现了多线程并行处理IO事件
时间点0: 服务器启动↓
Boss Group创建 → Boss EventLoop创建 → Boss Selector创建↓
ServerSocketChannel创建 → 注册到Boss Selector (监听OP_ACCEPT)↓
时间点1: 客户端连接↓
Boss Selector检测到OP_ACCEPT事件↓
创建SocketChannel → 分配给Worker Group↓
Worker EventLoop将SocketChannel注册到自己的Selector (监听OP_READ)↓
时间点2: 客户端发送数据↓
Worker Selector检测到OP_READ事件 → 处理数据
总结:
在Netty中:
- 每个EventLoop(线程)都有自己的Selector
- Channel一旦注册到某个EventLoop,就终身绑定
- 所有对该Channel的操作都在同一个线程中执行
这样设计确保了:
- 无锁化:没有线程竞争
- 顺序性:消息按发送顺序处理
2、如何解决粘包拆包问题
待补……
3、零拷贝
待补……
