網絡IO和磁盤IO詳解


1. 緩存IO

 

       緩存I/O又被稱作標准I/O,大多數文件系統的默認I/O操作都是緩存I/O。在Linux的緩存I/O機制中,數據先從磁盤復制到內核空間的緩沖區,然后從內核空間緩沖區復制到應用程序的地址空間。

       讀操作:操作系統檢查內核的緩沖區有沒有需要的數據,如果已經緩存了,那么就直接從緩存中返回;否則從磁盤中讀取,然后緩存在操作系統的緩存中。

       寫操作:將數據從用戶空間復制到內核空間的緩存中。這時對用戶程序來說寫操作就已經完成,至於什么時候再寫到磁盤中由操作系統決定,除非顯示地調用了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主要延時由: 服務器響應延時 + 帶寬限制 + 網絡延時 + 跳轉路由延時 + 本地接收延時 決定。(一般為幾十到幾千毫秒,受環境干擾極大)

所以兩者一般來說網絡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緩存區復制到網卡緩存,然后將其通過網絡發出

圖片描述

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資源。

sendfile

圖片描述

通過sendfile傳送文件只需要一次系統調用,當調用 sendfile時:
1、首先通過DMA copy將數據從磁盤讀取到kernel buffer中
2、然后通過CPU copy將數據從kernel buffer copy到sokcet buffer中
3、最終通過DMA copy將socket buffer中數據copy到網卡buffer中發送
sendfile與read/write方式相比,少了 一次模式切換一次CPU copy。但是從上述過程中也可以發現從kernel buffer中將數據copy到socket buffer是沒必要的。

為此,Linux2.4內核對sendfile做了改進,下圖所示
圖片描述
改進后的處理過程如下:
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 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淺析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM