网络IO和磁盘IO详解

1. 缓存IO

 

       缓存I/O又被称做标准I/O,大多数文件系统的默认I/O操做都是缓存I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,而后从内核空间缓冲区复制到应用程序的地址空间。linux

       读操做:操做系统检查内核的缓冲区有没有须要的数据,若是已经缓存了,那么就直接从缓存中返回;不然从磁盘中读取,而后缓存在操做系统的缓存中。nginx

       写操做:将数据从用户空间复制到内核空间的缓存中。这时对用户程序来讲写操做就已经完成,至于何时再写到磁盘中由操做系统决定,除非显示地调用了sync同步命令(详情参考《【珍藏】linux 同步IO: sync、fsync与fdatasync》)。数据库

       缓存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主要延时由: 服务器响应延时 + 带宽限制 + 网络延时 + 跳转路由延时 + 本地接收延时 决定。(通常为几十到几千毫秒,受环境干扰极大)app

因此二者通常来讲网络IO延时要大于磁盘IO的延时。

用Redis做缓存是由于,Redis就是设计来作缓存的阿。

Reids做缓存的几大优点:

1, 简单的K-V式数据存储方式,单一的 get set 模式比传统SQL性能提高显著

2, 纯in mem db 形式,将数据缓存在内存中,减小服务器磁盘IO时间。

更新一下数据源:

ref :

《大型网站技术架构:核心原理与案例分析》 

 

做者:李晨曦
连接:https://www.zhihu.com/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 sequentially from memory 250,000 ns

Round trip within same datacenter 500,000 ns

Disk seek 10,000,000 ns

Read 1 MB sequentially 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,后者只对写数据有效,它将写入内核缓冲区的数据当即写入磁盘,将机器故障时数据的丢失减小到最小,可是它仍然要通过内核缓冲区。

sendfile/零拷贝(网络I/O,kafka用到此特性)

普通的网络传输步骤以下:

1)操做系统将数据从磁盘复制到操做系统内核的页缓存中
2)应用将数据从内核缓存复制到应用的缓存中
3)应用将数据写回内核的Socket缓存中
4)操做系统将数据从Socket缓存区复制到网卡缓存,而后将其经过网络发出

图片描述

一、当调用read系统调用时,经过DMA(Direct Memory Access)将数据copy到内核模式
二、而后由CPU控制将内核模式数据copy到用户模式下的 buffer中
三、read调用完成后,write调用首先将用户模式下 buffer中的数据copy到内核模式下的socket buffer中
四、最后经过DMA copy将内核模式下的socket buffer中的数据copy到网卡设备中传送。

从上面的过程能够看出,数据白白从内核模式到用户模式走了一圈,浪费了两次copy,而这两次copy都是CPU copy,即占用CPU资源。

sendfile

图片描述

经过sendfile传送文件只须要一次系统调用,当调用 sendfile时:
一、首先经过DMA copy将数据从磁盘读取到kernel buffer中
二、而后经过CPU copy将数据从kernel buffer copy到sokcet buffer中
三、最终经过DMA copy将socket buffer中数据copy到网卡buffer中发送
sendfile与read/write方式相比,少了 一次模式切换一次CPU copy。可是从上述过程当中也能够发现从kernel buffer中将数据copy到socket buffer是不必的。

为此,Linux2.4内核对sendfile作了改进,下图所示
图片描述
改进后的处理过程以下:
一、DMA copy将磁盘数据copy到kernel buffer中
二、向socket buffer中追加当前要发送的数据在kernel buffer中的位置和偏移量
三、DMA gather copy根据socket buffer中的位置和偏移量直接将kernel buffer中的数据copy到网卡上。
通过上述过程,数据只通过了2次copy就从磁盘传送出去了。(事实上这个Zero copy是针对内核来说的,数据在内核模式下是Zero-copy的)。
当前许多高性能http server都引入了sendfile机制,如nginx,lighttpd等。

FileChannel.transferTo(Java中的零拷贝)

Java NIO中FileChannel.transferTo(long position, long count, WriteableByteChannel target)方法将当前通道中的数据传送到目标通道target中,在支持Zero-Copy的linux系统中,transferTo()的实现依赖于 sendfile()调用。

图片描述

传统方式对比零拷贝方式:

图片描述

整个数据通路涉及4次数据复制和2个系统调用,若是使用sendfile则能够避免屡次数据复制,操做系统能够直接将数据从内核页缓存中复制到网卡缓存,这样能够大大加快整个过程的速度。

大多数时候,咱们都在向Web服务器请求静态文件,好比图片、样式表等,根据前面的介绍,咱们知道在处理这些请求的过程当中,磁盘文件的数据先要通过内核缓冲区,而后到达用户内存空间,由于是不须要任何处理的静态数据,因此它们又被送到网卡对应的内核缓冲区,接着再被送入网卡进行发送。

数据从内核出去,绕了一圈,又回到内核,没有任何变化,看起来真是浪费时间。在Linux 2.4的内核中,尝试性地引入了一个称为khttpd的内核级Web服务器程序,它只处理静态文件的请求。引入它的目的便在于内核但愿请求的处理尽可能在内核完成,减小内核态的切换以及用户态数据复制的开销。

同时,Linux经过系统调用将这种机制提供给了开发者,那就是sendfile()系统调用。它能够将磁盘文件的特定部分直接传送到表明客户端的socket描述符,加快了静态文件的请求速度,同时也减小了CPU和内存的开销。

在OpenBSD和NetBSD中没有提供对sendfile的支持。经过strace的跟踪看到了Apache在处理151字节的小文件时,使用了mmap()系统调用来实现内存映射,可是在Apache处理较大文件的时候,内存映射会致使较大的内存开销,得不偿失,因此Apache使用了sendfile64()来传送文件,sendfile64()是sendfile()的扩展实现,它在Linux 2.4以后的版本中提供。

这并不意味着sendfile在任何场景下都能发挥显著的做用。对于请求较小的静态文件,sendfile发挥的做用便显得不那么重要,经过压力测试,咱们模拟100个并发用户请求151字节的静态文件,是否使用sendfile的吞吐率几乎是相同的,可见在处理小文件请求时,发送数据的环节在整个过程当中所占时间的比例相比于大文件请求时要小不少,因此对于这部分的优化效果天然不十分明显

 Zero-Copy&sendfile浅析

相关文章
相关标签/搜索