⽹络IO和磁盘IO详解
1. 缓存IO
缓存I/O⼜被称作标准I/O,⼤多数⽂件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应⽤程序的地址空间。
读操作:操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中。
写操作:将数据从⽤户空间复制到内核空间的缓存中。这时对⽤户程序来说写操作就已经完成,⾄于什么时候再写到磁盘中由操作系统决定,除⾮显⽰地调⽤了sync同步命令(详情参考《》)。
缓存I/O的优点:1)在⼀定程度上分离了内核空间和⽤户空间,保护系统本⾝的运⾏安全;2)可以减少读盘的次数,从⽽提⾼性能。
缓存I/O的缺点:在缓存 I/O 机制中,DMA ⽅式可以将数据直接从磁盘读到页缓存中,或者将数据从页缓存直接写回到磁盘上,⽽不能直接在应⽤程序地址空间和磁盘之间进⾏数据传输,这样,数据在传输过程中需要在应⽤程序地址空间(⽤户空间)和缓存(内核空间)之间进⾏多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是⾮常⼤的。
2. 直接IO
直接IO就是应⽤程序直接访问磁盘数据,⽽不经过内核缓冲区,这样做的⽬的是减少⼀次从内核缓冲区到⽤户程序缓存的数据复制。⽐如说数据库管理系统这类应⽤,它们更倾向于选择它们⾃⼰的缓存机制,因为数据库管理系统往往⽐操作系统更了解数据库中存放的数据,数据库管理系统可以提供⼀种更加有效的缓存机制来提⾼数据库中数据的存取性能。
直接IO的缺点:如果访问的数据不在应⽤程序缓存中,那么每次数据都会直接从磁盘加载,这种直接加载会⾮常缓存。通常直接IO与异步IO结合使⽤,会得到⽐较好的性能。(异步IO:当访问数据的线程发出请求之后,线程会接着去处理其他事,⽽不是阻塞等待)
下图分析了写场景下的DirectIO和BufferIO:
⾸先,磁盘IO主要的延时是由(以15000rpm硬盘为例):机械转动延时(机械磁盘的主要性能瓶颈,平均为2ms)+ 寻址延时(2~3ms)+ 块传输延时(⼀般4k每块,40m/s的传输速度,延时⼀般为0.1ms) 决定。(平均为5ms)
⽽⽹络IO主要延时由:服务器响应延时 + 带宽限制 + ⽹络延时 + 跳转路由延时 + 本地接收延时决定。(⼀般为⼏⼗到⼏千毫秒,受环境⼲扰极⼤)
所以两者⼀般来说⽹络IO延时要⼤于磁盘IO的延时。
⽤Redis作缓存是因为,Redis就是设计来做缓存的阿。
Reids作缓存的⼏⼤优势:
1, 简单的K-V式数据存储⽅式,单⼀的 get t 模式⽐传统SQL性能提升显著
2, 纯in mem db 形式,将数据缓存在内存中,减少服务器磁盘IO时间。
更新⼀下数据源:
ref :
《⼤型⽹站技术架构:核⼼原理与案例分析》
作者:李晨曦
链接:www.zhihu/question/47589908/answer/114768530
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,⾮商业转载请注明出处。
Google的Jeff Dean给的⼀些数据(⼀个talk的ppt, "Designs, Lessons and Advice from Building Large Distributed Systems" 23页),可以看到1Gbps的⽹络⽐硬盘的bandwidth⾼了很多,记住这些数据对设计⾼性能系统和对系统的性能估算很有帮助。
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns
Mutex lock/unlock 25 ns
Main memory reference 100 ns
Compress 1K bytes with Zippy 3,000 ns
Send 2K bytes over 1 Gbps network 20,000 ns
Read 1 MB quentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Disk ek 10,000,000 ns
Read 1 MB quentially from disk 20,000,000 ns
Send packet CA->Netherlands->CA 150,000,000 ns
PIO与DMA
有必要简单地说说慢速I/O设备和内存之间的数据传输⽅式。
PIO
我们拿磁盘来说,很早以前,磁盘和内存之间的数据传输是需要CPU控制的,也就是说如果我们读取磁盘⽂件到内存中,数据要经过CPU存储转发,这种⽅式称为PIO。显然这种⽅式⾮常不合理,需要占⽤⼤量的CPU时间来读取⽂件,造成⽂件访问时系统⼏乎停⽌响应。
DMA
后来,DMA(直接内存访问,Direct Memory Access)取代了PIO,它可以不经过CPU⽽直接进⾏磁盘和内存的数据交换。在DMA模式下,CPU只需要向DMA控制器下达指令,让DMA控制器来处理数据的传送即
可,DMA控制器通过系统总线来传输数据,传送完毕再通知CPU,这样就在很⼤程度上降低了CPU占有率,⼤⼤节省了系统资源,⽽它的传输速度与PIO的差异其实并不⼗分明显,因为这主要取决于慢速设备的速度。
可以肯定的是,PIO模式的计算机我们现在已经很少见到了。
标准⽂件访问⽅式
当应⽤程序调⽤read接⼝时,操作系统检查在内核的⾼速缓存有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有,则从磁盘中读取,然后缓存在操作系统的缓存中。
应⽤程序调⽤write接⼝时,将数据从⽤户地址空间复制到内核地址空间的缓存中,这时对⽤户程序来说,写操作已经完成,⾄于什么时候再写到磁盘中,由操作系统决定,除⾮显⽰调⽤了sync同步命令。
内存映射(减少数据在⽤户空间和内核空间之间的拷贝操作,适合⼤量数据传输)
Linux内核提供⼀种访问磁盘⽂件的特殊⽅式,它可以将内存中某块地址空间和我们要指定的磁盘⽂件相关联,从⽽把我们对这块内存的访问转换为对磁盘⽂件的访问,这种技术称为内存映射(Memory Mapping)。
操作系统将内存中的某⼀块区域与磁盘中的⽂件关联起来,当要访问内存中的⼀段数据时,转换为访问⽂件的某⼀段数据。这种⽅式的⽬的同样是减少数据从内核空间缓存到⽤户空间缓存的数据复制操作,因为这两个空间的数据是共享的。
内存映射是指将硬盘上⽂件的位置与进程逻辑地址空间中⼀块⼤⼩相同的区域⼀⼀对应,当要访问内存中⼀段数据时,转换为访问⽂件的某⼀段数据。这种⽅式的⽬的同样是减少数据在⽤户空间和内核空间之间的拷贝操作。当⼤量数据需要传输的时候,采⽤内存映射⽅式去访问⽂件会获得⽐较好的效率。
使⽤内存映射⽂件处理存储于磁盘上的⽂件时,将不必再对⽂件执⾏I/O操作,这意味着在对⽂件进⾏处理时将不必再为⽂件申请并分配缓存,所有的⽂件缓存操作均由系统直接管理,由于取消了将⽂件数据加载到内存、数据从内存到⽂件的回写以及释放内存块等步骤,使得内存映射⽂件在处理⼤数据量的⽂件时能起到相当重要的作⽤。
在⼤多数情况下,使⽤内存映射可以提⾼磁盘I/O的性能,它⽆须使⽤read()或write()等系统调⽤来访问⽂件,⽽是通过mmap()系统调⽤来建⽴内存和磁盘⽂件的关联,然后像访问内存⼀样⾃由地访问⽂件。
有两种类型的内存映射,共享型和私有型,前者可以将任何对内存的写操作都同步到磁盘⽂件,⽽且所有映射同⼀个⽂件的进程都共享任意⼀个进程对映射内存的修改;后者映射的⽂件只能是只读⽂件,所以不可以将对内存的写同步到⽂件,⽽且多个进程不共享修改。显然,共享型内存映射的效率偏低,因为如果⼀个⽂件被很多进程映射,那么每次的修改同步将花费⼀定的开销。
直接I/O(绕过内核缓冲区,⾃⼰管理I/O缓存区)
在Linux 2.6中,内存映射和直接访问⽂件没有本质上差异,因为数据从进程⽤户态内存空间到磁盘都要经过两次复制,即在磁盘与内核缓冲区之间以及在内核缓冲区与⽤户态内存空间。
引⼊内核缓冲区的⽬的在于提⾼磁盘⽂件的访问性能,因为当进程需要读取磁盘⽂件时,如果⽂件内容已经在内核缓冲区中,那么就不需要再次访问磁盘;⽽当进程需要向⽂件中写⼊数据时,实际上只是写到了内核缓冲区便告诉进程已经写成功,⽽真正写⼊磁盘是通过⼀定的策略进⾏延迟的。
然⽽,对于⼀些较复杂的应⽤,⽐如数据库服务器,它们为了充分提⾼性能,希望绕过内核缓冲区,由⾃
⼰在⽤户态空间实现并管理I/O缓冲区,包括缓存机制和写延迟机制等,以⽀持独特的查询机制,⽐如数据库可以根据更加合理的策略来提⾼查询缓存命中率。另⼀⽅⾯,绕过内核缓冲区也可以减少系统内存的开销,因为内核缓冲区本⾝就在使⽤系统内存。
应⽤程序直接访问磁盘数据,不经过操作系统内核数据缓冲区,这样做的⽬的是减少⼀次从内核缓冲区到⽤户程序缓存的数据复制。这种⽅式通常是在对数据的缓存管理由应⽤程序实现的数据库管理系统中。
直接I/O的缺点就是如果访问的数据不在应⽤程序缓存中,那么每次数据都会直接从磁盘进⾏加载,这种直接加载会⾮常缓慢。通常直接I/O跟异步I/O结合使⽤会得到较好的性能。
Linux提供了对这种需求的⽀持,即在open()系统调⽤中增加参数选项O_DIRECT,⽤它打开的⽂件便可以绕过内核缓冲区的直接访问,这样便有效避免了CPU和内存的多余时间开销。
顺便提⼀下,与O_DIRECT类似的⼀个选项是O_SYNC,后者只对写数据有效,它将写⼊内核缓冲区的数据⽴即写⼊磁盘,将机器故障时数据的丢失减少到最⼩,但是它仍然要经过内核缓冲区。
ndfile/零拷贝(⽹络I/O,kafka⽤到此特性)
1)操作系统将数据从磁盘复制到操作系统内核的页缓存中
2)应⽤将数据从内核缓存复制到应⽤的缓存中
3)应⽤将数据写回内核的Socket缓存中
4)操作系统将数据从Socket缓存区复制到⽹卡缓存,然后将其通过⽹络发出
1、当调⽤read系统调⽤时,通过DMA(Direct Memory Access)将数据copy到内核模式
2、然后由CPU控制将内核模式数据copy到⽤户模式下的 buffer中
3、read调⽤完成后,write调⽤⾸先将⽤户模式下 buffer中的数据copy到内核模式下的socket buffer中
4、最后通过DMA copy将内核模式下的socket buffer中的数据copy到⽹卡设备中传送。
从上⾯的过程可以看出,数据⽩⽩从内核模式到⽤户模式⾛了⼀圈,浪费了两次copy,⽽这两次copy都是CPU copy,即占⽤CPU资源。
通过ndfile传送⽂件只需要⼀次系统调⽤,当调⽤ ndfile时:
1、⾸先通过DMA copy将数据从磁盘读取到kernel buffer中
2、然后通过CPU copy将数据从kernel buffer copy到sokcet buffer中
3、最终通过DMA copy将socket buffer中数据copy到⽹卡buffer中发送
ndfile与read/write⽅式相⽐,少了⼀次模式切换⼀次CPU copy。但是从上述过程中也可以发现从kernel buffer中将数据copy到socket buffer是没必要的。
为此,Linux2.4内核对ndfile做了改进,下图所⽰
改进后的处理过程如下:
1、DMA copy将磁盘数据copy到kernel buffer中
2、向socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量
3、DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到⽹卡上。
经过上述过程,数据只经过了2次copy就从磁盘传送出去了。(事实上这个Zero copy是针对内核来讲的,数据在内核模式下是Zero-copy的)。
当前许多⾼性能http rver都引⼊了ndfile机制,如nginx,lighttpd等。
Java NIO中ansferTo(long position, long count, WriteableByteChannel target)⽅法将当前通道中的数据传送到⽬标通道target中,在⽀持Zero-Copy的linux系统中,transferTo()的实现依赖于 ndfile()调⽤。
传统⽅式对⽐零拷贝⽅式:
整个数据通路涉及4次数据复制和2个系统调⽤,如果使⽤ndfile则可以避免多次数据复制,操作系统可以直接将数据从内核页缓存中复制到⽹卡缓存,这样可以⼤⼤加快整个过程的速度。
⼤多数时候,我们都在向Web服务器请求静态⽂件,⽐如图⽚、样式表等,根据前⾯的介绍,我们知道在处理这些请求的过程中,磁盘⽂件的数据先要经过内核缓冲区,然后到达⽤户内存空间,因为是不需要任何处理的静态数据,所以它们⼜被送到⽹卡对应的内核缓冲区,接着再被送⼊⽹卡进⾏发送。
数据从内核出去,绕了⼀圈,⼜回到内核,没有任何变化,看起来真是浪费时间。在Linux 2.4的内核中,尝试性地引⼊了⼀个称为khttpd的内核级Web服务器程序,它只处理静态⽂件的请求。引⼊它的⽬的便在于内核希望请求的处理尽量在内核完成,减少内核态的切换以及⽤户态数据复制的开销。
同时,Linux通过系统调⽤将这种机制提供给了开发者,那就是ndfile()系统调⽤。它可以将磁盘⽂件的特定部分直接传送到代表客户端的socket描述符,加快了静态⽂件的请求速度,同时也减少了CPU和内存的开销。
在OpenBSD和NetBSD中没有提供对ndfile的⽀持。通过strace的跟踪看到了Apache在处理151字节
的⼩⽂件时,使⽤了mmap()系统调⽤来实现内存映射,但是在Apache 处理较⼤⽂件的时候,内存映射会导致较⼤的内存开销,得不偿失,所以Apache使⽤了ndfile64()来传送⽂件,ndfile64()是ndfile()的扩展实现,它在Linux 2.4之后的版本中提供。
这并不意味着ndfile在任何场景下都能发挥显著的作⽤。对于请求较⼩的静态⽂件,ndfile发挥的作⽤便显得不那么重要,通过压⼒测试,我们模拟100个并发⽤户请求151字节的静态⽂件,是否使⽤ndfile的吞吐率⼏乎是相同的,可见在处理⼩⽂件请求时,发送数据的环节在整个过程中所占时间的⽐例相⽐于⼤⽂件请求时要⼩很多,所以对于这部分的优化效果⾃然不⼗分明显。