背景
618压测过程中,涉及大规格的参数化文件上传平台,由于文件过大超过2G,在平台上传过程中经常失败,超时,重试也要等老半天,这就会造成人力资源等待影响工作效率。那么应该怎么做才能快速上传,如何提高文件上传性能以及做到就算失败了再次重试也能从上次中断的地方继续上传提升系统的容错能力呢 ?我学习整理了一些优化思路,在此分享给大家,请君看下文分解~~~
从业务流程上优化
为提升大文件上传效率和容错性,从流程上可实现3个功能步骤。
- 大文件分片上传。
- 支持断点续传。
- 上传进度展示。
从系统底层上优化
总思路原则
降时延,提并发
- 减少磁盘的工作量,如pagecache技术(磁盘高速缓存)。
- 减少CPU的工作量,如直接IO技术。
- 提高内存的利用率,如零拷贝技术。
详细解析
要优化文件上传性能,必须先了解服务器文件上传的过程。
过程
发送文件上传请求后,首先磁盘读取文件,然后通过网络协议发送给客户端。
客户端请求从磁盘找到文件位置,然后从磁盘把部分文件(切分后)读入缓冲区,然后通过网络把数据发送给客户端,如下图:
问题
1.上下文切换
一次收发过程涉及到4次用户态和内核态的上下文切换,没处理缓冲区大小的数据需要一次read调用和一次write调用,每次调用都需要从用户态切换到内核态。然后等内核态完成任务后,再切换回用户态,如果文件大,分片较多,读取次数比较多,上下文切换的成本不容小觑。
2.多次内存拷贝
磁盘-》pagecache
pagecache->用户缓冲区
用户缓冲区->socket缓冲区
socket缓冲区->网卡
多次内存拷贝,消耗过多的CPU资源,降低系统并发能力,表现为CPU系统态占用过高
解决思路方案
零拷贝技术
降低上下文切换
首先读取磁盘文件的上下文切换是一定会有的,因为读取磁盘和操作网卡都是由操作系统内核完成,所以我们再执行read或write这种系统调用时,一定会经过2次上下文切换:先从用户态切换到内核态,当内核态任务完成后,再切换回用户态交由进程代码执行。所以想要降低上下文切换就需减少系统调用次数。解决办法是把read和write两次系统调用合并成一次,在内核态中完成磁盘与网卡的数据交换操作。
减少内存拷贝次数
一次收发过程有两次与物理设备相关的内存拷贝是必不可少的:1.把磁盘的数据拷贝到内存,2.把内存的数据拷贝到网卡,而用户缓冲区相关的拷贝则不是必须的,故可以在内核读取文件后,直接把pagecache中的数据拷贝socket缓冲区,这样就只有2次上下文切换和3次内存拷贝,如果网卡支持SG-DMA技术,还可以把拷贝到socket缓冲区的步骤省略,如下图:
PageCache磁盘高速缓存技术
根据时间局部性原理,刚被访问到的数据在短时间被再次访问的概率高,通常将最近访问的数据放到pagecache中,当空间不足时根据LRU算法淘汰最久未被访问的数据。
pagecache提供预读功能,但不适合传输大文件,因为大文件容易把pagecache占满,而且由于文件过大,文件中某一部分的数据被再次访问的概率低,这会导致大文件在pagecache中没有享受到缓存的优势。
异步IO&直接IO技术
异步IO可以把读操作分为两个部分,前部向内核发起读请求,但不用等待数据就位就返回,然后可以继续处理其他任务,当内核把磁盘中的数据拷贝到进程缓冲区后,会通知进程去处理数据,异步IO是不会阻塞用户进程的,能提升并发操作。
对于磁盘,异步IO只支持直接IO,即应用程序绕过pagecache,不经过内核缓存区,直接访问磁盘中的数据,从而减少了内核缓存与用户程序之间的数据拷贝。
因为直接IO不适用pagecache缓存,所以享受不到内核针对pagecache做的一些优化,这是不足之处。故直接IO适应的应用场景:1.应用程序已经自己实现了磁盘文件的缓存,不需要pagecache再次进行缓存,引发额外的性能消耗;2.高并发下传输大文件,因为大文件难以命中pagecache缓存,又会影响其他热点小文件缓存。
方法论
从系统层面的优化思路上看,大文件交给异步IO和直接IO技术处理,小文件交给零拷贝技术处理。