緩存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以及內存開銷是很是大的。安全
直接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 :
《大型網站技術架構:核心原理與案例分析》
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有必要簡單地說說慢速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緩存區
)在Linux 2.6中,內存映射和直接訪問文件沒有本質上差別,由於數據從進程用戶態內存空間到磁盤都要通過兩次複製,即在磁盤與內核緩衝區之間以及在內核緩衝區與用戶態內存空間。
引入內核緩衝區的目的在於提升磁盤文件的訪問性能,由於當進程須要讀取磁盤文件時,若是文件內容已經在內核緩衝區中,那麼就不須要再次訪問磁盤;而當進程須要向文件中寫入數據時,實際上只是寫到了內核緩衝區便告訴進程已經寫成功,而真正寫入磁盤是經過必定的策略進行延遲的。
然而,對於一些較複雜的應用,好比數據庫服務器,它們爲了充分提升性能,但願繞過內核緩衝區,由本身在用戶態空間實現並管理I/O緩衝區,包括緩存機制和寫延遲機制等,以支持獨特的查詢機制,好比數據庫能夠根據更加合理的策略來提升查詢緩存命中率。另外一方面,繞過內核緩衝區也能夠減小系統內存的開銷,由於內核緩衝區自己就在使用系統內存。
應用程序直接訪問磁盤數據,不通過操做系統內核數據緩衝區,這樣作的目的是減小一次從內核緩衝區到用戶程序緩存的數據複製。這種方式一般是在對數據的緩存管理由應用程序實現的數據庫管理系統中。
直接I/O的缺點就是若是訪問的數據不在應用程序緩存中,那麼每次數據都會直接從磁盤進行加載,這種直接加載會很是緩慢。一般直接I/O跟異步I/O結合使用會獲得較好的性能。
Linux提供了對這種需求的支持,即在open()系統調用中增長參數選項O_DIRECT,用它打開的文件即可以繞過內核緩衝區的直接訪問,這樣便有效避免了CPU和內存的多餘時間開銷。
順便提一下,與O_DIRECT相似的一個選項是O_SYNC,後者只對寫數據有效,它將寫入內核緩衝區的數據當即寫入磁盤,將機器故障時數據的丟失減小到最小,可是它仍然要通過內核緩衝區。
網絡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時:
一、首先經過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等。
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的吞吐率幾乎是相同的,可見在處理小文件請求時,發送數據的環節在整個過程當中所佔時間的比例相比於大文件請求時要小不少,因此對於這部分的優化效果天然不十分明顯。