差一点
我们就擦肩而过了
有趣
有用
有态度
导学问题:
1.服务器通过网络传输数据时,系统有几次拷贝?
2.大文件和小文件各有什么传输特点?
什么是零拷贝
零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。
从一个案例说起
某天你接到公司下发的一个安排,完成一个类似ftp文件传输的功能,要求传输效率高,cpu占用低。典型的要马儿跑得快又不给马吃草。
第一版实现:
最直接的方法:从网络请求中找出文件在磁盘中的路径后,如果这个文件比较大,假设有 100MB,可以在内存中分配 10KB 的缓冲区,再把文件分成一万份,每份只有 10KB,这样,从文件的起始位置读入10KB 到缓冲区,再通过网络 API 把这 10KB 发送到客户端。接着重复一万次,直到把完整的文件都发送完毕。
示意图如下:
现在我们可以看到1->2->3->4的整个过程一共经历了四次拷贝的方式,**但是真正消耗资源和浪费时间的是第二次和第三次,因为这两次都需要经过我们的CPU拷贝,而且还需要内核态和用户态之间的来回切换。**上下文切换的成本并不小,虽然一次切换仅消耗几十纳秒到几微秒,但高并发服务会放大这类时间的消耗。
这个方案做了 1 万次内存拷贝,对 100MB 文件拷贝的字节数也翻了 4 倍,到了1000MB。CPU资源是多么宝贵,要处理大量的任务,还要去拷贝大量的数据。无疑,过多的内存拷贝无谓地消耗了 CPU 资源。我们要想办法降低上下文切换频率和内存拷贝次数。
第二版实现:
引入mmap技术,mmap可以实现用户空间和内核空间数据共享,从而避免减少一次内核空间到用户空间数据拷贝动作,在数据量很大的时候,效率提升尤其明显。
示意图如下:
注意:这里的零拷贝其实是根据内核状态划分的,在这里没有经过CPU的拷贝,数据在用户态的状态下,经历了零次拷贝,所以才叫做零拷贝,但不是说不拷贝。所以现在内核依然依然会有一次从缓存(pagecache)拷贝到socket缓冲区的过程。这里我们能不能想办法优化呢?
第三版实现:
借助DMA(Direct Memory Access)直接内存访问技术,可以帮助我们再减少一次cpu拷贝过程,它可以让数据直接从缓存传输给网卡,整个拷贝过程减小到了两次。
示意图如下:
这已经比之前大大提高了我们的传输效率,最后我们再来看pagecache能不能再优化一下。首先磁盘比内存的速度慢许多,所以先把磁盘文件读到内存,就能用读内存替换读磁盘。而且在读取过程中,会预读一部分的磁盘文件以方便下次读取。但是在读取大文件时,预读就失效了,因为大文件会占据全部的pagecache。也就是说这一次拷贝几乎是徒劳的,并没有获得明显性能的提高。所以,高并发场景下,为了防止 PageCache 被大文件占满后不再对小文件产生作用,大文件不应使用 PageCache,进而也不应使用零拷贝技术处理。
第四版实现:
在面临高并发、大文件的数据传输时,我们需要绕过 PageCache。Linux系统下支持直接IO,所谓直接IO就是指点对点直接传输,没有中间商赚差价,性能效率极高。通过直接IO往往会搭配异步IO来应对高并发。
示意图如下:
当然,直接 IO 也有一定的缺点。除了缓存外,内核(IO 调度算法)会试图缓存尽量多的 连续 IO 在 PageCache 中,最后合并成一个更大的 IO 再发给磁盘,这样可以减少磁盘的 寻址操作;另外,内核也会预读后续的 IO 放在 PageCache 中,减少磁盘操作。直接 IO 绕过了 PageCache,所以无法享受这些性能提升。
有了直接 IO 后,异步 IO 就可以无阻塞地读取文件了。现在,大文件由异步 IO 和直接 IO 处理,小文件则交由零拷贝处理,至于判断文件大小的阈值可以灵活配置。
OK,先到这里。
觉得不错,请点个在看