1. Netty基础
1.1. Netty是什么
- 开源项目,异步、基于事件驱动的网络应用框架,事件驱动的概念为 Netty根据客户端事件做响应
- 主要应用于开发基于TCP协议的网络IO程序,Netty不去修改网络底层,而是提供更高的网络设施。比如高性能服务器端/客户端,P2P程序
- Netty基于NIO实现
1.2. 应用场景
- 常应用与构建高性能RPC框架,主要用在分布式服务群中的互相调用,比如Dubbo
- Hadoop,大数据框架,数据在多个计算节点传输。为了提高性能也是使用Netty来构建高性能的网络IO层
- 构建高性能游戏交互服务器,Netty提供TCP/UDP,HHTP的协议栈。开发者可以基于Netty做私有协议
1.3. Java中的网络IO模型
这里简单介绍即可,java中有三种网络IO模型, AIO NIO BIO
- BIO 阻塞IO 来一个请求开一个线程去处理,性能低且无法支撑高并发,就算使用线程池和阻塞队列也无法优化这种底层处理模式带来的线程利用率低的问题。每个线程同一时间只能处理一个任务。
- NIO 同步非阻塞IO 这种做法主要从加了一个多路复用器Selector提高线程利用率,一个线程可以处理多个客户端的请求。可以理解为操作系统的时间轮转片操作。并且通过Buffer + Channel来提高处理性能 NIO是基于缓冲区编程的,从缓冲区读数据的时候,游标可以在缓冲区前后移动提高数据处理灵活性,BIO只能顺序读取流里面的数据。

- AIO 异步非阻塞IO 操作系统处理完客户端的read/write后通知线程去处理后续工作,AIO目前并未得到推广,此处不详细描述。
- 总结 使用 Java NIO 构建的 IO 程序,它的工作模式是:主动轮训 IO 事件,IO 事件发生后程序的线程主动处理 IO 工作,这种模式也叫做 Reactor 模式。使用 Java AIO 构建的 IO 程序,它的工作模式是:将 IO 事件的处理托管给操作系统,操作系统完成 IO 工作之后会通知程序的线程去处理后面的工作,这种模式也叫做 Proactor 模式。
1.4. NIO总结
BIO是通过流方式处理数据,NIO是通过缓冲区(也称为块)处理数据,块IO比流效率高,且BIO是基于字符或字节流操作,NIO基于Channel和Buffer操作。

一个selector对应一个处理线程,一个selector对应多个channel。channel正常情况下对应一个buffer,有时候可能对应多个,buffer的本质是内存块,底层是数组
Selector会根据不同事件在channel上切换,buffer是双向的,可读可写,channel也是双向的,可以写入也可以流出
1.4.1. 缓冲区(Buffer)
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- DoubleBuffer
- FloatBuffer
ByteBuffer是支持类型化数据存取的,可以往ByteBuffer塞byte、long、int等数据,读取的时候要做好类型匹配,否则会抛异常
ByteBuffer还有一个MappedByteBuffer的子类,可以让文件内容直接在堆外内存中被修改,同步到文件的操作由NIO来完成。具体实现等下次发文进行讲解
1.4.2. 通道(Channel)
- FileChannel 文件读写
- DatagramChannel:用于 UDP 数据包收发
- ServerSocketChannel:用于服务端 TCP 数据包收发
- SocketChannel:用于客户端 TCP 数据包收发
1.4.3. 选择器(Selector)
多路复用关键,Channel上有事件后,Selector就会读取事件并调用线程去处理
public abstract class Selector implements Closeable {
......
/**
* 得到一个选择器对象
*/
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
......
/**
* 返回所有发生事件的 Channel 对应的 SelectionKey 的集合,通过
* SelectionKey 可以找到对应的 Channel
*/
public abstract Set<SelectionKey> selectedKeys();
......
/**
* 返回所有 Channel 对应的 SelectionKey 的集合,通过 SelectionKey
* 可以找到对应的 Channel
*/
public abstract Set<SelectionKey> keys();
......
/**
* 监控所有注册的 Channel,当其中的 Channel 有 IO 操作可以进行时,
* 将这些 Channel 对应的 SelectionKey 找到。参数用于设置超时时间
*/
public abstract int select(long timeout) throws IOException;
/**
* 无超时时间的 select 过程,一直等待,直到发现有 Channel 可以进行
* IO 操作
*/
public abstract int select() throws IOException;
/**
* 立即返回的 select 过程
*/
public abstract int selectNow() throws IOException;
......
/**
* 唤醒 Selector,对无超时时间的 select 过程起作用,终止其等待
*/
public abstract Selector wakeup();
......
}
1.5. 零拷贝
这里不做讲解,之前已有发文专门讲解过零拷贝
2. Netty架构与原理
2.1. 为什么要有Netty
- java的NIO类库太多,上手难度大,需要手动处理断连、网络拥塞等问题,Netty帮助解决
- java NIO 有bug,epoll事件出问题会导致选择器空轮询导致CPU资源被浪费
- 零拷贝提高性能
2.2. 多种Reactor线程模式
2.2.1. 单Reactor线程模式

工作流程如下
接收请求 -》select判断请求类型,dispatch做分发
优点在于没有多线程竞争和线程切换的问题,逻辑简单。缺点在于容易出现单点故障且性能不行,容易阻塞
比如有个请求的处理卡住,其他请求无法处理。或者线程意外终止,整个模块进行崩溃
因此,单 Reactor 单线程模式使用场景为:客户端的数量有限,业务处理非常快速,比如 Redis 在业务处理的时间复杂度为 O(1)的情况。
2.2.2. 单 Reactor 多线程模式

这波,这波是把活丢给外包做,外包好了就告诉我一声,外包做的这段时间里我就可以一直接任务了,反正都是丢外包做,这就是单Reactor多线程模式
这种模式的优点是可以充分的利用多核 cpu 的处理能力,缺点是多线程数据共享和控制比较复杂,Reactor 处理所有的事件的监听和响应,在单线程中运行,面对高并发场景还是容易出现性能瓶颈。
2.2.3. 主从 Reactor 多线程模式

在单Reactor多线程模式的基础上多加一个Reacitr子线程做优化,一方面是避免Reactor主线程出问题导致系统瘫痪。另一方面也是提高并发能力
这种模式的优点是:
1)MainReactor 线程与 SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成后续的业务处理。
2)MainReactor 线程与 SubReactor 线程的数据交互简单, MainReactor 线程只需要把新连接传给 SubReactor 线程,SubReactor 线程无需返回数据。
3)多个 SubReactor 线程能够应对更高的并发请求。
这种模式的缺点是编程复杂度较高。但是由于其优点明显,在许多项目中被广泛使用,包括 Nginx、Memcached、Netty 等。
这种模式也被叫做服务器的 1+M+N 线程模式,即使用该模式开发的服务器包含一个(或多个,1 只是表示相对较少)连接建立线程+M 个 IO 线程+N 个业务处理线程。这是业界成熟的服务器程序设计模式。
2.3. Netty的内核处理流程

Netty 把网络 IO 拆成"接活"和"干活"两条流水线:BossGroup 只负责在门口揽客(建连接),WorkerGroup 只负责在屋里端菜(读/写数据),每条流水线里又是多个永动机一样的 NioEventLoop 线程在死循环跑三步曲:select → process → runTask。
分步拆解
- 角色划分
• BossGroup(门口揽客)
• WorkerGroup(屋里端菜)
它们本质都是 NioEventLoopGroup------一个装着若干 NioEventLoop 的"线程池"。 - NioEventLoop 是什么
一条永不停止的"事件处理线程",内部带一个 Selector,用来监听一堆 Channel 上的 IO 事件。 - BossGroup 的工作节奏(死循环三步曲)
3.1 select:轮询 ServerSocketChannel 上的 OP_ACCEPT 事件------"有客人敲门吗?"
3.2 processSelectedKeys:真有人来,就跟他握手,生成一条 NioSocketChannel(代表这条连接),然后把它"派单"给某个 WorkerNioEventLoop 的 Selector。
3.3 runAllTasks:顺手把任务队列里的杂活(如定时任务、用户自定义任务)干完。 - WorkerGroup 的工作节奏(同样死循环三步曲)
4.1 select:轮询自己管的所有 NioSocketChannel 上的 OP_READ / OP_WRITE------"哪条连接需要读或写?"
4.2 processSelectedKeys:真有事,就在这条 Channel 上读数据、解码、业务处理、编码、写回;过程中会沿着 Pipeline 依次跑各种处理器(编解码、日志、业务逻辑......)。
4.3 runAllTasks:同样把队列里的其他任务清掉。 - Pipeline 的角色
每个 Channel 绑一条 Pipeline,Pipeline 里串了一排处理器;数据像流水一样从一端进,经过层层处理器,从另一端出。
一句话再总结
BossGroup 的 NioEventLoop 只干"接连接"这一件事,WorkerGroup 的 NioEventLoop 只干"读写数据"这一件事;每个线程都在跑"select 事件 → 处理事件 → 干杂活"的无限循环,Pipeline 则在处理事件时提供可插拔的处理器链。
学习内容来源以及图片来源