youyichannel

志于道,据于德,依于仁,游于艺!

0%

如何传输大文件?

直接给出结论:

  • 传输大文件时,使用「异步I/O+直接I/O」;
  • 传输小文件时,使用「零拷贝技术」;

为什么呢?

什么是 PageCache?

前面几篇文章谈论了零拷贝,文件传输过程第一步都是需要先把磁盘文件数据拷贝到「内核缓冲区中」,这个内核缓冲区就是PageCache,即磁盘高速缓存。

DMA 会把磁盘里的数据搬运到内存中,这样就可以使用读取内存的方式来替换读取磁盘了。但是内存空间是比较小的,内存注定了只能拷贝磁盘里的一小部分数据,选择存储的数据一定是热点数据,这样才有意义。

由此,PageCache 的优点主要是两个:

  • 缓存最近被访问的数据;
  • 数据预读。

但是,在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用。因为在传输大文件时,如果使用到 PageCache,PageCache 很快就会被占满;另外,由于文件太大,可能某些部分的文件数据被再次访问的概率比较低,这就会带来两个问题:

  • PageCache 由于长时间被大文件占据,其他热点的小文件无法充分利用到 PageCache,磁盘读写性能下降;
  • PageCache 中的大文件数据,由于没有利用到缓存的优势,还耗费了 DMA 拷贝到 PageCache 这个过程。

因此,针对大文件的传输,不应该使用 PageCache,也就是说不应该使用零拷贝技术。

大文件传输使用什么方式?

原先使用 read 方法读取文件时,进程会阻塞在 read 方法调用上,因为需要等待数据的返回。

那么,对于阻塞的问题,可以使用「异步I/O」来解决:

异步I/O将读操作分为两部分:

  • 前半部分,内核向磁盘发起读请求,可以不等待数据就位就可以返回,进程此时可以处理其他任务;
  • 后半部分,当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的通知,再去处理数据;

可以看出,异步I/O没有使用到PageCache。

不使用 PageCache 的 I/O 方式叫「直接 I/O」,使用 PageCache 的 I/O 叫「缓存 I/O」。通常对于磁盘来说,异步 I/O 只支持直接 I/O。

由此,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术

直接 I/O 常见的应用场景

  • 应用程序已经实现了磁盘数据的缓存,那么可以不需要 PageCache 再次缓存,减少额外的性能损耗。在 MySQL 数据库中,可以通过参数设置开启直接 I/O,默认是不开启;
  • 传输大文件的时候,由于大文件难以命中 PageCache 缓存,而且会占满 PageCache 导致「热点」文件无法充分利用缓存,从而增大了性能开销,因此,这时应该使用直接 I/O。

此外,由于直接 I/O 绕过了 PageCache,就无法享受内核的这两点的优化:

  • 内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 PageCache 中,最后合并成一个更大的 I/O 请求再发给磁盘,这样做是为了减少磁盘的寻址操作;
  • 内核也会预读后续的 I/O 请求放在 PageCache 中,一样是为了减少对磁盘的操作。

所以,传输大文件的时候,使用「异步 I/O + 直接 I/O」了,就可以无阻塞地读取文件了。

Nginx 中可以根据文件的大小来使用不同的方式:

location /xxx/ { 
sendfile on;
aio on;
directio 1024m;
}

当文件大小大于 directio 值后,使用「异步 I/O + 直接 I/O」,否则使用「零拷贝技术」。