写点什么

大厂面试必问,支撑百万并发的“零拷贝”技术,你真的理解吗?

发布于: 54 分钟前
大厂面试必问,支撑百万并发的“零拷贝”技术,你真的理解吗?

今日分享开始啦,请大家多多指教~

1、摘要

零拷贝的“零”是指用户态和内核态间 copy 数据的次数为零。

传统的数据 copy(文件到文件、client 到 server 等)涉及到四次用户态内核态切换、四次 copy。四次 copy 中,两次在用户态和内核态间 copy 需要 CPU 参与、两次在内核态与 IO 设备间 copy 为 DMA 方式不需要 CPU 参与。零拷贝避免了用户态和内核态间的 copy、减少了两次用户态内核态间的切换。

零拷贝可以提高数据传输效率,但对于需要在用户传输过程中对数据进行加工的场景(如加密)就不适合使用零拷贝。

使用 Zero Copy 前后对比:

2、介绍

java 的 zero copy 多在网络应用程序中使用。Java 的 libaries 在 linux 和 unix 中支持 zero copy,关键的 api 是 java.nio.channel.FileChannel 的 transferTo(),transferFrom()方法。我们可以用这两个方法来把 bytes 直接从调用它的 channel 传输到另一个 writable byte channel,中间不会使 data 经过应用程序,以便提高数据转移的效率。

许多 web 应用都会向用户提供大量的静态内容,这意味着有很多 data 从硬盘读出之后,会原封不动的通过 socket 传输给用户。这种操作看起来可能不会怎么消耗 CPU,但是实际上它是低效的:kernal 把数据从 disk 读出来,然后把它传输给 user 级的 application,然后 application 再次把同样的内容再传回给处于 kernal 级的 socket。这种场景下,application 实际上只是作为一种低效的中间介质,用来把 disk file 的 data 传给 socket。

data 每次穿过 user-kernel boundary,都会被 copy,这会消耗 cpu,并且占用 RAM 的带宽。幸运的是,你可以用一种叫做 Zero-Copy 的技术来去掉这些无谓的 copy。应用程序用 zero copy 来请求 kernel 直接把 disk 的 data 传输给 socket,而不是通过应用程序传输。Zero copy 大大提高了应用程序的性能,并且减少了 kernel 和 user 模式的上下文切换。

使用 kernel buffer 做中介(而不是直接把 data 传到 user buffer 中)看起来比较低效(多了一次 copy)。然而实际上 kernel buffer 是用来提高性能的。在进行读操作的时候,kernel buffer 起到了预读 cache 的作用。当写请求的 data size 比 kernel buffer 的 size 小的时候,这能够显著地提升性能。在进行写操作时,kernel buffer 的存在可以使得写请求完全异步。

悲剧的是,当请求的 data size 远大于 kernel buffer size 的时候,这个方法本身变成了性能的瓶颈。因为 data 需要在 disk,kernel buffer,user buffer 之间拷贝很多次(每次写满整个 buffer)。

而 Zero copy 正是通过消除这些多余的 data copy 来提升性能。

3、传统方式及涉及到的上下文切换

通过网络把一个文件传输给另一个程序,在 OS 的内部,这个 copy 操作要经历四次 user mode 和 kernel mode 之间的上下文切换,甚至连数据都被拷贝了四次,如下图:

具体步骤如下:

  • read() 调用导致一次从 user mode 到 kernel mode 的上下文切换。在内部调用了 sys_read() 来从文件中读取 data。第一次 copy 由 DMA (direct memory access)完成,将文件内容从 disk 读出,存储在 kernel 的 buffer 中。

  • 然后请求的数据被 copy 到 user buffer 中,此时 read()成功返回。调用的返回触发了第二次 context switch: 从 kernel 到 user。至此,数据存储在 user 的 buffer 中。

  • send() Socket call 带来了第三次 context switch,这次是从 user mode 到 kernel mode。同时,也发生了第三次 copy:把 data 放到了 kernel adress space 中。当然,这次的 kernel buffer 和第一步的 buffer 是不同的 buffer。

  • 最终 send() system call 返回了,同时也造成了第四次 context switch。同时第四次 copy 发生,DMA egine 将 data 从 kernel buffer 拷贝到 protocol engine 中。第四次 copy 是独立而且异步的。

4、zero copy 方式及涉及的上下文转换

在 linux 2.4 及以上版本的内核中(如 linux 6 或 centos 6 以上的版本)修改了 socket buffer descriptor,使网卡支持 gather operation,通过 kernel 进一步减少数据的拷贝操作。这个方法不仅减少了 context switch,还消除了和 CPU 有关的数据拷贝。user 层面的使用方法没有变,但是内部原理却发生了变化:

transferTo()方法使得文件内容被 copy 到了 kernel buffer,这一动作由 DMA engine 完成。 没有 data 被 copy 到 socket buffer。取而代之的是 socket buffer 被追加了一些 descriptor 的信息,包括 data 的位置和长度。然后 DMA engine 直接把 data 从 kernel buffer 传输到 protocol engine,这样就消除了唯一的一次需要占用 CPU 的拷贝操作。

5、Java NIO 零拷贝示例

NIO 中的 FileChannel 拥有 transferTo 和 transferFrom 两个方法,可直接把 FileChannel 中的数据拷贝到另外一个 Channel,或直接把另外一个 Channel 中的数据拷贝到 FileChannel。该接口常被用于高效的网络/文件的数据传输和大文件拷贝。

在操作系统支持的情况下,通过该方法传输数据并不需要将源数据从内核态拷贝到用户态,再从用户态拷贝到目标通道的内核态,同时也减少了两次用户态和内核态间的上下文切换,也即使用了“零拷贝”,所以其性能一般高于 Java IO 中提供的方法。

5.1、通过网络把一个文件从 client 传到 server:




5.2、文件到文件的零拷贝:


今日份分享已结束,请大家多多包涵和指点!

用户头像

还未添加个人签名 2021.04.20 加入

Java工具与相关资料获取等WX: gsh950924(备注来源)

评论

发布
暂无评论
大厂面试必问,支撑百万并发的“零拷贝”技术,你真的理解吗?