netty 高性能原因

多路复用

Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector、epoll,可以同时并发处理成百上千个客户端 Channel。
由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 IO 阻塞导致的线程挂起。

异步通讯 IO

Netty 采用了异步通信模式,一个IO 线程可以并发处理N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 IO 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

零拷贝技术

netty 接收和发送 Bytebuffer 采用堆外直接内存进行 socket 读写。
传统网络传输情况下,网卡首先接收到数据,并将数据拷贝至操作系统的缓冲区,此时内核态切换至用户态,应用程序读取内核缓冲区的数据,用户态切换成内核态,cpu 将数据拷贝至用户缓存空间中,这样用户程序就可以操作这些数据。用户处理完数据之后,向用户缓冲区中发送数据并刷新缓存。此时用户态转换成内核态,cpu 将数据拷贝至操作内核 socket缓冲区,利用 DMA技术将数据拷贝至网卡然后发送出去。
netty 框架使用Bytebuf 来接收处理这些数据,netty 读取数据的时候采用内存映射 mmap 技术,此时用户态可以直接访问内核缓冲区的数据并不需要 cpu 进行拷贝,减少了一次上下文切换以及数据的拷贝。
当 netty 发送数据时,采用 Bytebuf,利用底层的 sendfile 方法(linux2.1 之后提供的方法,java 中对应的是FileChannel.transferTo方法)文件映射的技术直接访问向内核缓冲区发送数据,减少一次上下文切换以及 cpu 的拷贝。采用零拷贝技术减少了 2 次上下文切换以及避免了数据发送过程中 cpu 参与的数据拷贝降低的 cpu 的负载使得系统性能得以提升。

内存池技术

随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。

高效的 Reactor 线程模型

常用的 Reactor 线程模型有三种,Reactor 单线程模型, Reactor 多线程模型, 主从 Reactor 多线程模型。

Reactor 单线程模型

Reactor 单线程模型,指的是所有的 IO 操作都在同一个 NIO 线程上面完成,NIO 线程的职责如下:

  1. 作为 NIO 服务端,接收客户端的 TCP 连接;

  2. 作为 NIO 客户端,向服务端发起 TCP 连接;

  3. 读取通信对端的请求或者应答消息

  4. 向通信对端发送消息请求或者应答消息。

由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。从架构层面看,一个 NIO 线程确实可以完成其承担的职责。例如,通过Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程将消息发送给客户端。