【Netty的零拷贝是怎样实现的】
Netty的零拷贝是怎样实现的
快速理解
在操作系统中,零copy指的是避免在用户态(User-space) 与内核态(Kernel-space)之间来回copy数据。
前面我有介绍什么是零拷贝: 什么是零拷贝
而Netty的零拷贝模型和操作系统中的零拷贝模型并不完全一样。它主要指的是在操作数据的时间,不需要将数据buffer从一个内存区域拷贝到另一个内存区域。少了一次内存的拷贝,CPU就会得到提升。
Netty的零拷贝主要体现在以下5个方面:
1、直接使用堆外内存:
避免JVM堆内存到堆外内存的数据拷贝。
2、CompositeByteBuffer类:
可以组合多个Buffer对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个Buffer合并成一个大的Buffer。
3、Unpooled.wrappedBuffer:
通过Unpooled.wrappedBuffer可以将byte数组包装成ByteBuf对象,包装过程中不会产生内存拷贝。
4、ByteBuf.slice:
ByteBuf.slice操作与Unpooled.wrappedBuffer相反,slice操作可以将一个ByteBuf对象切分为多个ByteBuf对象,切分过程中不会产生内存拷贝,底层共享一个byte数组的存储空间。
5、使用FileRegion实现文件传输:
FileRegion底层封装了FileChannel#transferTO()方法,可以将文件缓冲区的数据之间传输到目标Channel,避免内核缓冲区到用户缓冲区之间的数据拷贝,这属于操作系统级别的零拷贝。
拓展
堆外内存
我们都知道,Java在将数据发送出去的时候,会现将数据从堆内存拷贝到堆外内存,然后才会将堆外内存再拷贝到内核态,进行消息的收发,代码如下:
static int write(FileDescriptor paramFileDescriptor, ByteBuffer paramByteBuffer, long paramLong, NativeDispatcher paramNativeDispatcher ) throws IOException {
//如果是直接内存,则直接写入
if((paramByteBuffer instanceof DirectBuffer)) {
return writeFormNativeBuffer(paramFileDescriptor , paramByteBuffer , paramLong , paramNativeDispatcher );
}
//……否则,先把数据拷贝到直接内存中
ByteBuffer localByteBuffer = Util.getTemporaryDirectBuffer(k);
try{
localByteBuffer.put(paramByteBuffer);
localByteBuffer.filp();
paramByteBuffer.position(i);
int m = writeFormNativeBuffer(paramFileDescriptor , localByteBuffer , paramLong , paramNativeDispatcher );
}
}
所以,,我们发现,假如我们在收发报文的时间使用直接内存,那么就可以减少一次内存拷贝,Netty就是这么做的。
Netty在通信层进行字节流的接收和发送的时候,如果应用允许Unsafe访问,则会采用DirectByteBuf进行转换,也就是堆外的直接内存,代码如下:
public ByteBuf ioBuffer (int initialCapacity) {
if (PlatformDependent . hasUnsafe() || isDirectBufferPooled()) {
return directBuffer(initialCapacity);
}
return heapBuffer(initialCapacity);
}
CompositeByteBuffer
考虑一种场景,当一个数据包被拆成了两个字节流通过TCP传输过来以后,那么对于接收者的机器来说,为了方便解析,它需要新建一个ByteeBuf将这两个字节流重组成一个新的数据包,如图:
那么这种情况下,我们如果直接将两个字节流拷贝到一个新的字节流中,显然会浪费空间和时间,所以Netty推出了CompositeByteBuffer,专门用于拷贝ByteBuf。看图:
从图中我们可以看到,实际的Buf还是两个,只不过Netty通过CompositeByteBuffer将老的buf通过指针组合映射到新的Buf中,减少了一次拷贝过程。
Unpooled.wrappedBuffer
Unpooled.wrappedBuffer 是创建 CompositeByteBuffer 对象的另一种推荐做法。
Unpooled.wrappedBuffer 方法可以将不同的数据源的一个或者多个数据包装成一个大的Buf对象,其中数据源的类型包括 byte[] 、ByteBuf、ByteBuffer。包装的过程中不会发生数据拷贝操作,包装后生成的ByteBuf对象和原始ByteBuf对象是共享底层的byte数组。
ByteBuf.slice
ByteBuf.slice 和 Unpooled.wrappedBuffer 的逻辑正好相反,ByteBuf.slice 是将一个ByteBuf对象切分成多个共享同一个底层存储的ByteBuf对象。
使用FileRegion文件传输
Netty使用FileRegion实现文件传输的零copy,而FileRegion其实是基于Java底层的。
FileChannel#tranferTo()方法实现的。它可以根据操作系统直接将文件缓冲区的数据发送到目标Channel,底层借助了sendFile能力避免传统通过循环write方式导致的内核拷贝问题。所以FileRegion 是操作系统级别的零拷贝。
JavaDoc的注释如下:
This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel .
Many operating systems can transfer bytes directly from thefilesystem cache to the target channel without actually copying them.
sendFile
JDK原生的FileChannel#tranferTo()方法其实是基于Linux的sendFile方法,通过该方法,数据可以直接在内核空间内部进行I/O传输,从而省去了数据在用户空间和内核空间之间的来回拷贝。工作原理如下图:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!