在目前主流的系统中,其实大多数都是数据密集型系统,所以设计数据密集型应用一书非常经典,推荐一读。而大多数遇到的问题都是存储问题。CPU、内存 因为本身的读写速度比较快,所以磁盘就成为了一个性能瓶颈。
针对磁盘优化的技术层出不穷,比如零拷贝、直接IO、异步IO、PageCache等,本质都是为了提升系统的吞吐量、降低操作时延。减少磁盘和CPU设备的工作量。
文件传输过程
假设,我们请求从服务端下载文件,服务端的整体处理流程是 用户进程从本地磁盘获取文件,然后将文件通过socket缓冲区发送到网络中,如果一个文件是320MB,一次拷贝32KB的话,那么整体就是拷贝1W次。
而从图中可以分析出一次文件拷贝 从用户态到内核态切换了4次。
- 1.用户态发起read文件,切换到内核态,然后从磁盘中读取文件到pageCache中。
- 2.内核态返回数据到用户态的用户缓冲区中。
- 3.用户态调用write写入到socket缓冲区中。
- 4.最后返回结果切回用户态。
而如果一次文件拷贝是4次的话,那么整体就是4W次。放在高并发系统下,虽然一次可能需要几十纳秒到几微秒。但是当请求数在几十万、百万级别的情况下,这个情况会被放大。
可以很容易分析出,问题的瓶颈在于用户态到内核态的上下文切换以及内存拷贝次数过多。
零拷贝
上面我们通过分析,其实性能瓶颈主要在于文件的拷贝次数,以及多次的上下文切换。
上下文切换的原因主要在于,调用read、write函数,需要系统函数进行处理。如果想减少上下文切换次数,可以将read、write进行合并,在内核中直接完成数据的交换。
减少内存拷贝次数,多余的地方在于,从pageCache到用户缓冲区,以及用户缓冲区到socket缓冲区。
优化点1. 直接从pagecache拷贝到socket缓冲区。这样整个过程就只有3次拷贝、2次上下文切换。
而如果网卡支持DMA技术,可以直接从pagecache写入到网卡中。只有2次拷贝。
所以总结一下,零拷贝的本质是通过减少上下文切换以及在用户态0次数据拷贝 以及来提升整体的文件读写性能。
而零拷贝技术是系统提升的新函数,可以接受文件描述符和TCP socket作为输入参数,可以完全在内核态完成数据的拷贝,减少了系统上下文切换,也减少了内存拷贝次数。
可以最大化利用socket缓冲区。socket缓冲区可用空间是动态变化的。
零拷贝让我们可以不必关心socket缓冲区的大小。将性能提升至少一倍以上。
PageCache
pagechache的作用主要是为了提升从磁盘读取数据的速度,并且可以采用预读功能,比如读一个文件1MB,先读取前64KB,那么后64KB也会写入到PageCahce中,并且pageCache有一定的时间局部性,以及数据淘汰策略,LRU。
但是针对于大文件读取,因为有预读功能,所以可能大文件会占满pageCache,导致小文件不能利用PageCache。所以大文件场景下不应该使用PageCahe和零拷贝。
异步IO+直接IO
那么针对大文件来说,一般采用异步IO和直接IO替换零拷贝技术。
如果是同步进行IO的话,整个过程用户进程其实是一直阻塞等待,不能很好的利用系统的并发性。
而异步IO,在发起读之后,直接返回,由内核准备好数据之后,通知用户进行进行操作,在整个过程中,不需要完全阻塞等待。
但是异步IO是不支持将数据写入到pageCache中。绕过pageCache称为直接IO。对于磁盘,异步IO只支持直接IO。
所以针对于大文件由异步IO和直接IO处理,小文件由零拷贝处理。至于文件阈值需要灵活配置。
小结
本篇主要介绍了零拷贝技术,零拷贝通过减少上下文切换次数,以及数据拷贝次数,提升整体的文件读写能力。并且还利用PageCache 来提升从磁盘读取文件的性能。而pageCache缓冲了最近访问的数据,并且有自己的数据淘汰策略。LRU。进一步提升了零拷贝技术。pageCache还提升了整体的写性能,先将数据写入到PageCache中,然后将数据异步到磁盘。
不对针对于大文件来说,建议直接使用异步IO+直接IO。
所以总结一下,为了提升文件传输的性能。基本上就是三个点。减少磁盘的工作量(pageCache缓存)、减少CPU的工作量(直接IO)
、提高内存利用率(零拷贝)。
并且很多中间件技术也通过零拷贝来提升性能,比如Kakfa、Netty等。