ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Netty 源码分析系列(八)Netty 如何实现零拷贝,字节跳动Java岗经典面试真题

2021-11-13 15:02:42  阅读:172  来源: 互联网

标签:Netty slice Java 真题 FileChannel 源码 ByteBuf 拷贝


1、Java提供 mmap/write 方式

Java NIO 提供的MappedByteBuffer,用于提供mmap/write方式。

Java NlO 中 的Channel (通道)就相当于操作系统中的内核缓冲区,有可能是读缓冲区,也有可能是网络缓冲区,而Buffer就相当于操作系统中的用户缓冲区。

以下是一个MappedByteBuffer的使用案例:

File file = new File(“jw.txt”);

try {

FileChannel fc = new RandomAccessFile(file, “rw”).getChannel();

MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, 0, file.length());

map.put(“jiangwang”.getBytes());

fc.position(file.length());

map.clear();

fc.write(map);

} catch (IOException e) {

e.printStackTrace();

}

复制代码

上述示例中,通过FileChannel.map()方法来创建MappedByteBuffer,该方法底层就是调用Linux的 mmap()实现的。

该方法将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射。这种方式适合读取大文件,同时也能对文件内容进行更改,但是如果其后要通过SocketChannel发送,还是需要CPU进行数据的拷贝。

使用MappedByteBuffer,如果是小文件,执行效率不高;而且MappedByteBuffer只能通过调用FileChannelmap()取得,再没有其他方式。因此,Java 中设计MappedByteBuffer就是为大文件准备的。

2、Java 提供 sendfile 方式

Java FileChannel.transferTo() 底层实现就是通过 Linux 的 sendfile实现的。该方法直接将当前通道内容传输到另一个通道,没有涉及Buffer的任何操作。

以下是FileChannel.transferTo() 的使用示例:

//使用sendfile:读取磁盘文件,并网络发送

FileChannel sourceChannel = new RandomAccessFile(source, “rw”).getChannel();

SocketChannel socketChannel = SocketChannel.open(sa);

sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);

复制代码

Netty 实现零拷贝


Netty 中的零拷贝的实现是基于 Java 的,换言之,底层也是基于操作系统实现的。相对于

【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

Java 中的零拷贝而言,Netty 的零拷贝更多的是偏向于优化数据操作的概念。

Netty 中的零拷贝体现在以下几个方面:

  • Netty 提供了CompositeByteBuf类,它可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的复制。

  • 通过wrap操作,可以将 byte [] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象,进而避免了复制操作。

  • ByteBuf支持slice操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的复制。

  • 通过FileRegion包装的FileChannel.transferTo()实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了通过循环 while方式导致的内存复制问题。

从上面几个方法可以看出,前三个方法都是广义零拷贝,其实现方式都是为了减少不必要的数据复制,偏向于应用层数据优化操作。而第四个方法,FileRegion

包装的FileChannel.transferTo(),才是真正的零拷贝(狭义零拷贝)。

下面分别来看其每一种实现。

1、CompositeByteBuf 方式

CompositeByteBuf 将多个ByteBuf合并为一个逻辑上的ByteBuf,类似于用一个链表,把分散的多个ByteBuf通过引用连接起来。分散的多个ByteBuf在内存中可能是大小各异、互不相连的区域,通过链表串联起来,作为一块逻辑上的大区域。而在实际数据读取时,还是会去各自每一小块上读取。

下图展示了 CompositeByteBuf 的原理:

image-20210813093354168

以下是 CompositeByteBuf 使用的代码示例:

ByteBuf header = …

ByteBuf body = …

CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer();

compositeBuffer.addComponents(true, header,body);

复制代码

2、wrap 方式

可以通过 wrap操作来实现零拷贝。

通过 wrap 操作,可以将 byte [] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象。

例如,通过 Unpooled.wrappedBuffer方法来将 bytes 包装成为一个UnpooledHeapByteBuf对象,而在包装的过程中,是不会有复制操作的。即最后生成的 ByteBuf 对象是和 bytes 数组共用了同一个存储空间,对 bytes 的修改也会反映到 ByteBuf 对象中。

以下是Unpooled.wrappedBuffer使用的代码示例:

ByteBuf header = …

ByteBuf body = …

ByteBuf allByteBuf = Unpooled.wrappedBuffer(header,body);

复制代码

3、slice 方式

可以通过 slice 方式实现零拷贝,原理图如下:

image-20210813095044982

通过 Slice 操作,将ByteBuf分解为多个共享同一个存储区域的ByteBuf。slice 恰好是将一整块区域,划分成逻辑上的独立小区域,在读取每个逻辑上的小区域时,实际会去按 slice(int index,int length)方法中的indexlength去读取原内存 buffer 的数据。

以下是 slice 使用的示例代码:

ByteBuf bytebuf = …

ByteBuf header = bytebuf.slice(0,5);

ByteBuf body = bytebuf.slice(5,10);

复制代码

4、 FileRegion 方式

FileRegion底层包装的是 Java 的FileChannel.transferTo()实现文件传输,因此可以直接将文件缓冲区的数据发送到目标Channel。这种方式才是真正操作系统级别的零拷贝。

以下是FileRegion使用的代码示例:

public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

RandomAccessFile raf = null;

标签:Netty,slice,Java,真题,FileChannel,源码,ByteBuf,拷贝
来源: https://blog.csdn.net/m0_63174811/article/details/121304651

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有