按关键词阅读:
如果你追溯 Kafka 文件传输的代码 , 你会发现 , 最终它调用了 Java NIO 库里的transferTo方法:
如果 Linux 系统支持sendfile()系统调用 , 那么transferTo()实际上最后就会使用到sendfile()系统调用函数 。
曾经有大佬专门写过程序测试过 , 在同样的硬件条件下 , 传统文件传输和零拷拷贝文件传输的性能差异 , 你可以看到下面这张测试数据图 , 使用了零拷贝能够缩短65%的时间 , 大幅度提升了机器传输数据的吞吐量 。
本文图片
数据来源于:https://developer.ibm.com/articles/j-zerocopy/
另外 , Nginx 也支持零拷贝技术 , 一般默认是开启零拷贝技术 , 这样有利于提高文件传输的效率 , 是否开启零拷贝技术的配置如下:
sendfile 配置的具体意思:
设置为 on 表示 , 使用零拷贝技术来传输文件:sendfile, 这样只需要 2 次上下文切换 , 和 2 次数据拷贝 。
设置为 off 表示 , 使用传统的文件传输技术:read + write , 这时就需要 4 次上下文切换 , 和 4 次数据拷贝 。
当然 , 要使用 sendfile , Linux 内核版本必须要 2.1 以上的版本 。
PageCache 有什么作用?
回顾前面说道文件传输过程 , 其中第一步都是先需要先把磁盘文件数据拷贝「内核缓冲区」里 , 这个「内核缓冲区」实际上是磁盘高速缓存(PageCache) 。
由于零拷贝使用了 PageCache 技术 , 可以使得零拷贝进一步提升了性能 , 我们接下来看看 PageCache 是如何做到这一点的 。
读写磁盘相比读写内存的速度慢太多了 , 所以我们应该想办法把「读写磁盘」替换成「读写内存」 。 于是 , 我们会通过 DMA 把磁盘里的数据搬运到内存里 , 这样就可以用读内存替换读磁盘 。
但是 , 内存空间远比磁盘要小 , 内存注定只能拷贝磁盘里的一小部分数据 。
那问题来了 , 选择哪些磁盘数据拷贝到内存呢?
我们都知道程序运行的时候 , 具有「局部性」 , 所以通常 , 刚被访问的数据在短时间内再次被访问的概率很高 , 于是我们可以用PageCache 来缓存最近被访问的数据 , 当空间不足时淘汰最久未被访问的缓存 。
所以 , 读磁盘数据的时候 , 优先在 PageCache 找 , 如果数据存在则可以直接返回;如果没有 , 则从磁盘中读取 , 然后缓存 PageCache 中 。
还有一点 , 读取磁盘数据的时候 , 需要找到数据所在的位置 , 但是对于机械磁盘来说 , 就是通过磁头旋转到数据所在的扇区 , 再开始「顺序」读取数据 , 但是旋转磁头这个物理动作是非常耗时的 , 为了降低它的影响 , PageCache 使用了「预读功能」 。
比如 , 假设 read 方法每次只会读32 KB的字节 , 虽然 read 刚开始只会读 0 ~ 32 KB 的字节 , 但内核会把其后面的 32~64 KB 也读取到 PageCache , 这样后面读取 32~64 KB 的成本就很低 , 如果在 32~64 KB 淘汰出 PageCache 前 , 进程读取到它了 , 收益就非常大 。
所以 , PageCache 的优点主要是两个:
缓存最近被访问的数据;
预读功能;
这两个做法 , 将大大提高读写磁盘的性能 。
但是 , 在传输大文件(GB 级别的文件)的时候 , PageCache 会不起作用 , 那就白白浪费 DMA 多做的一次数据拷贝 , 造成性能的降低 , 即使使用了 PageCache 的零拷贝也会损失性能 。
这是因为如果你有很多 GB 级别文件需要传输 , 每当用户访问这些大文件的时候 , 内核就会把它们载入 PageCache 中 , 于是 PageCache 空间很快被这些大文件占满 。
另外 , 由于文件太大 , 可能某些部分的文件数据被再次访问的概率比较低 , 这样就会带来 2 个问题:
PageCache 由于长时间被大文件占据 , 其他「热点」的小文件可能就无法充分使用到 PageCache , 于是这样磁盘读写的性能就会下降了;
稿源:(CSDN)
【】网址:http://www.shadafang.com/c/hn10049531C2020.html
标题:coding|原来 8 张图,就可以搞懂“零拷贝”了!( 四 )