Java与Netty实现高性能高并发( 二 )


JDK NIO通信模型如下所示:

Java与Netty实现高性能高并发

文章插图
 
图2-3 NIO的多路复用模型图
与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现 。这两种新增的通道都支持阻塞和非阻塞两种模式 。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式正好相反 。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择同步阻塞IO以降低编程复杂度 。但是对于高负载、高并发的网络应用,需要使用NIO的非阻塞模式进行开发 。
Netty架构按照Reactor模式设计和实现,它的服务端通信序列图如下:
Java与Netty实现高性能高并发

文章插图
图2-3 NIO服务端通信序列图
客户端通信序列图如下:
Java与Netty实现高性能高并发

文章插图
【Java与Netty实现高性能高并发】图2-4 NIO客户端通信序列图
Netty的IO线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端Channel,由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁IO阻塞导致的线程挂起 。另外,由于Netty采用了异步通信模式,一个IO线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升 。
2.2.2. 零拷贝
很多用户都听说过Netty具有“零拷贝”功能,但是具体体现在哪里又说不清楚,本小节就详细对Netty的“零拷贝”功能进行讲解 。
Netty的“零拷贝”主要体现在如下三个方面:
1) Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝 。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中 。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝 。
2) Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer 。
3) Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题 。
下面,我们对上述三种“零拷贝”进行说明,先看Netty 接收Buffer的创建:
Java与Netty实现高性能高并发

文章插图
图2-5 异步消息读取“零拷贝”
每循环读取一次消息,就通过ByteBufAllocator的ioBuffer方法获取ByteBuf对象,下面继续看它的接口定义:
Java与Netty实现高性能高并发

文章插图
图2-6 ByteBufAllocator 通过ioBuffer分配堆外内存
当进行Socket IO读写的时候,为了避免从堆内存拷贝一份副本到直接内存,Netty的ByteBuf分配器直接创建非堆内存避免缓冲区的二次拷贝,通过“零拷贝”来提升读写性能 。
下面我们继续看第二种“零拷贝”的实现CompositeByteBuf,它对外将多个ByteBuf封装成一个ByteBuf,对外提供统一封装后的ByteBuf接口,它的类定义如下:
Java与Netty实现高性能高并发

文章插图
图2-7 CompositeByteBuf类继承关系
通过继承关系我们可以看出CompositeByteBuf实际就是个ByteBuf的包装器,它将多个ByteBuf组合成一个集合,然后对外提供统一的ByteBuf接口,相关定义如下:
Java与Netty实现高性能高并发

文章插图
图2-8 CompositeByteBuf类定义
添加ByteBuf,不需要做内存拷贝,相关代码如下:
Java与Netty实现高性能高并发

文章插图
图2-9 新增ByteBuf的“零拷贝”
最后,我们看下文件传输的“零拷贝”:
Java与Netty实现高性能高并发

文章插图
图2-10 文件传输“零拷贝”
Netty文件传输DefaultFileRegion通过transferTo方法将文件发送到目标Channel中,下面重点看FileChannel的transferTo方法,它的API DOC说明如下:
Java与Netty实现高性能高并发


推荐阅读