零拷贝并非万能解决方案:重新定义数据传输的效率极限( 二 )


  1. 应用程序发起 read 系统调用,用户进程开始进行阻塞等待结果返回 。
  2. 此时内核会向磁盘发起 I/O 请求,磁盘收到请求后,开始寻址 。当磁盘数据准备好后,就会向内核发起 I/O 中断,告知内核磁盘数据已经准备好 。
  3. 内核收到中断信号后,将数据从磁盘控制器缓存区拷贝到 pageCache 缓冲区 。
  4. 最后,内核会将 pageCache 中的数据再次拷贝到用户缓冲区,也就是用户态的内存中,然后 read 调用返回 。
我们知道,既然有同步 IO,就一定有异步 IO 来解决阻塞的问题 。异步 IO 的工作方式如下图所示:
零拷贝并非万能解决方案:重新定义数据传输的效率极限

文章插图
图片
它将读操作分为两个部分:
  1. 第一部分是用户进程发起 IO 请求给内核,然后进程就不再关心该 IO 操作,而是继续处理其他任务 。
  2. 第二部分是当内核接收到中断信号后,将数据直接拷贝到用户缓冲区,并通知用户进程操作成功 。然后用户进程开始处理数据 。
我们发现在这个过程中,并没有涉及到将数据拷贝到 pageCache 中,因此使用异步方式绕开了 pageCache 。直接 IO 是指绕过 pageCache 的 IO 请求,而缓存 IO 是指使用 pageCache 的 IO 请求 。通常,对于磁盘而言,异步 IO 只支持直接 IO 。
正如前面所提到的,对于大文件的传输,不应该使用 PageCache,因为这可能会导致 PageCache 被大文件占据,从而使得"热点"小文件无法充分利用 PageCache 的优势 。
因此,在高并发的场景下,对于大文件传输,我们应该采用"异步 I/O + 直接 I/O"的方式来代替零拷贝技术 。
直接 I/O 有两种常见的应用场景:
  1. 首先,如果应用程序已经实现了磁盘数据的缓存,就不需要再次使用 PageCache 进行缓存,这样可以减少额外的性能损耗 。例如,在 MySQL 数据库中,可以通过参数设置来开启直接 I/O,避免重复的缓存操作,默认情况下是不开启的 。
  2. 其次,在传输大文件时,由于大文件很难命中 PageCache 的缓存,而且会占满 PageCache 导致"热点"文件无法充分利用缓存,增加了性能开销 。因此,在这种情况下,应该使用直接 I/O 来绕过 PageCache 的缓存,以提高性能 。
需要注意的是,直接 I/O 绕过了 PageCache,因此无法享受内核的两项优化 。
  1. 首先,内核的 I/O 调度算法会在 PageCache 中缓存尽可能多的 I/O 请求,然后将它们合并成一个更大的 I/O 请求发送给磁盘,以减少磁盘的寻址操作 。
  2. 其次,内核会预读后续的 I/O 请求并将其放入 PageCache 中,同样是为了减少对磁盘的操作 。这些优化在直接 I/O 中无法享受到 。
于是,当我们需要传输大文件时,我们可以利用异步 I/O 和直接 I/O 的组合来实现无阻塞的文件读取 。这种方式可以有效避免 PageCache 的影响,提高文件传输的效率 。
因此,在文件传输过程中,我们可以根据文件的大小来选择不同的优化方式,以提高传输效率 。对于大文件,使用异步 I/O 和直接 I/O 可以避免 PageCache 的影响;而对于小文件,则可以使用零拷贝技术来减少数据拷贝次数,提高传输速度 。
在 Nginx 中,我们可以通过以下配置来根据文件的大小选择不同的优化方式:
location /video/ {sendfile on;AIo on;directio 1024m; }在这个配置中,我们开启了 sendfile 选项,这允许 Nginx 使用零拷贝技术来传输文件 。同时,我们也启用了 aio 选项,这使得 Nginx 可以使用异步 I/O 来提高文件传输的效率 。
而通过设置 directio 参数为 1024m,我们告诉 Nginx 当文件大小超过 1024MB 时,使用直接 I/O 来进行文件传输 。这意味着在传输大文件时,Nginx 将使用异步 I/O 和直接 I/O 的组合来实现无阻塞的文件读取,避免了 PageCache 的影响 。而对于小文件,Nginx 将继续使用零拷贝技术,以减少数据拷贝次数,提高传输速度 。
/ 总结 /至此,我们的计算机基础专栏就结束了,不知道大家有没有发现,操作系统底层提供了丰富的解决方案来支持应用程序的复杂性和可扩展性 。对于任何工作中遇到的问题,我们都可以从操作系统的角度寻找解决方法 。


推荐阅读