當前位置:
首頁 > 最新 > Linux 下訪問 I/O 的各種方法,我們為 Scylla 選擇了哪個?為什麼?

Linux 下訪問 I/O 的各種方法,我們為 Scylla 選擇了哪個?為什麼?

當大多數伺服器應用程序開發人員想到 I/O 時,首先他們會考慮網路 I/O,因為大多數資源可以通過網路進行訪問:資料庫,對象存儲以及其他微服務。 然而,資料庫的開發人員也必須考慮文件 I/O。 本文介紹了可用的選擇及其權衡,以及為什麼 Scylla 選擇非同步直接 I/O(AIO/DIO)作為其訪問方法。

選擇訪問文件

一般來說,訪問 Linux 伺服器上的文件有四種選擇:讀/寫,mmap,直接 I/O(DIO)讀/寫和非同步直接 I/O (AIO/DIO)。

傳統讀/寫

從一開始就使用的傳統方法,就是使用 read(2)和 write(2)系統調用。而在最新的實現中,讀系統調用(或其許多變體之一 - pread,readv,preadv 等)要求內核讀取文件的一部分並將數據複製到調用進程地址空間中。如果所有請求的數據都在頁面緩存中,則內核會將其複製並立即返回;否則,它將安排磁碟將請求的數據讀入頁面緩存,阻止調用線程,並且當數據可用時,它將恢複線程並複製數據。另一方面,寫入通常 (註解1)會將數據複製到頁面緩存中;內核會將頁面緩存寫回磁碟一段時間。

Mmap

一種替代和更現代的方法是使用 mmap(2),系統調用將文件記錄到應用程序地址空間中。 這會導致一部分地址空間直接引用到包含文件數據的頁面緩存頁面。 在此準備步驟之後,應用程序可以使用處理器的存儲器讀和寫指令訪問文件數據。 如果請求的數據恰好在緩存中,則內核將被完全忽略,並且以內存速度執行讀取(或寫入)。 如果發生高速緩存未命中,則會發生頁錯誤,並且內核將活動線程置於休眠狀態,以便讀取該頁面的數據。 當數據最終可用時,存儲器管理單元被編程,使得新讀取的數據可被線程訪問,然後被喚醒。

Direct I/O (DIO)

傳統的讀/寫和 mmap 都涉及內核頁緩存,並將內核的 I/O 延遲調度。 當應用程序希望自己調度 I/O(至於原因我們稍後將解釋),它可以使用 Direct I/O。 這涉及使用 O_DIRECT 標誌打開文件; 進一步的活動將使用系統調用的正常讀寫序列,但是它們的行為已經改變:不是去訪問緩存,而是直接訪問磁碟,這意味著調用線程將無條件地進入休眠狀態。 此外,磁碟控制器將直接將數據複製到用戶空間,繞過內核。

非同步 direct I/O (AIO/DIO)

Direct I/O 的一種重構,非同步 Direct I/O 的行為與之非常類似,但不會使調用線程阻塞。相反,應用程序線程可使用 io_submit(2) 系統調用來調度 Direct I/O 操作,並且該線程不會阻塞;I/O 操作與正常線程並行運行。單獨的系統調用 io_getevents(2) 用於等待並收集已完成的 I/O 操作的結果。和 DIO 一樣,內核中的頁面緩存會被繞過,磁碟控制器負責將數據直接複製到用戶空間。

了解權衡

不同的訪問方法都有各自的特點,在其他方面有所不同。 表1總結了這些特點,詳細闡述如下。

緩存控制

讀/寫和 mmap 的緩存都是由內核負責。系統的大部分內存都被提供給頁面緩存。內核決定哪些頁面在內存不足時被刪除,以及頁面何時需要寫回磁碟,並且控制預讀。應用程序可以使用 madvise(2) 和 fadvise(2) 系統調用為內核提供一些指導。

讓內核控制緩存的巨大優點是內核開發人員在數十年來投入巨大的精力來調整緩存使用的演算法。這些演算法由數千種不同的應用程序使用,通常是有效的。然而,缺點是這些演算法是通用的,並沒有調整到對應的應用程序級別。內核必須判斷應用程序將如何運行,即使知道應用程序不同,但也無法幫助內核進行判斷。這會導致頁面被誤刪, I/O 排列出錯,或預先讀取的數據無法消耗。

複製和 MMU 活動

mmap 方法的優點之一是如果數據在緩存中,則內核將被完全繞過。內核不需要將數據從內核複製到用戶空間並返回,因此在該活動上花費的處理器周期較少。這有利於主要存在於緩存中的負載(例如,如果存儲大小與 RAM 大小的比率接近 1:1)。

然而,當數據不在緩存中時,mmap 的缺點就會顯現。當存儲大小與 RAM 大小的比例明顯高於 1:1 時,通常會發生這種情況。引入緩存的每個頁面都會導致另一個頁面被逐出。這些頁面必須先插入頁面表,之後又從表格中刪除;內核必須掃描頁面表以隔離停止活動的頁面,然後將其刪除。另外,mmap 需要頁面表的內存。在 x8 6處理器上,這需要映射文件大小的 0.2%。這似乎很低,但是如果應用程序的存儲比率為 100:1,則 20% 的內存(0.2%* 100)會被用於頁面表。

I/O 調度

讓內核控制緩存(使用 mmap 和 讀/寫訪問方法)的一個問題是應用程序丟失了 I/O 調度的控制。內核選擇其認為合適的數據塊,調度它進行寫入或讀取。這可能會導致以下問題:

寫入風暴:當內核調度大量寫入時,磁碟將被佔用很長時間,並影響讀取延遲。

內核無法區分「重要」 和 「不重要」 的 I/O。屬於後台任務的 I/O 可以壓倒前台任務,這將影響其延遲(注釋2)

通過繞過內核頁面緩存,應用程序承擔自己調度 I/O 的負荷。這並不意味著問題得到解決,而是意味著如果給予充分的關注和努力,問題可以得到解決。

當使用 Direct I/O 時,每個線程式控制制發出 I/O 的時間。而內核控制線程的運行時間,所以發出 I/O 的責任在內核和應用程序之間共享。使用 AIO/DIO,應用程序可以完全控制何時發出 I/O。

線程調度

使用 mmap 或讀/寫的 I/O 密集型應用程序無法猜測其緩存命中率是多少。因此,它必須運行大量的線程(明顯大於它運行的機器的核心數)。使用太少的線程,它們可能都在等待磁碟而使處理器未被充分利用。由於每個線程通常最多有一個磁碟 I/O,所以運行的線程數必須大約是存儲子系統的並發數乘以某個小的因子,以保持磁碟完全佔用。然而,如果緩存命中率足夠高,那麼這些大量的線程將互相爭奪有限數量的核心。

當使用直接 I/O 時,這個問題所有緩解,因為應用程序知道線程在 I/O 上何時被阻塞,何時可以運行,因此應用程序可以根據運行時條件調整運行的線程數。

使用 AIO/DIO,則應用程序可以完全控制正在運行的線程和等待 I/O(兩者完全分離),所以它可以輕易地調整到內存或磁碟綁定的條件或介於兩者之間的任意條件。

I/O 對齊

存儲設備都有一個塊長度;所有 I/O 必須以塊長度的倍數來執行,該值通常是 512 或 4096 位元組。使用讀/寫或 mmap 時,內核將自動執行對齊;在內核發出讀寫操作之前,小的讀或寫將被擴展到合適的塊邊界。

使用 DIO,執行塊對齊是由應用程序來決定的。這導致了一些複雜性,但也提供了一個優勢:即使在滿足 512 位元組的邊界,內核也將過度對齊到 4096 位元組的邊界,但是使用 DIO 的用戶應用程序可以觸發 512 位元組對齊的讀取,這樣可以為小的條目節省帶寬。

應用程序的複雜性

雖然前面的討論贊成 AIO/DIO 用於 I/O 密集型應用程序,但該方法具有顯著的成本:複雜性。將緩存管理的責任放在應用程序上意味著它可以比內核做出更好的選擇,並以較少的開銷做出那些選擇。然而,那些演算法需要編寫和測試。使用非同步 I/O 要求使用回調、協程或類似的方法去編寫應用程序,並且通常會降低許多可用庫的可重用性。

Scylla 和 AIO/DIO

憑藉 Scylla,我們選擇了性能最高的 AIO/DIO。為了隔離一些複雜性,我們編寫了Seastar,一個用於 I/O 密集型應用程序的高性能框架。Seastar 抽象了執行 AIO 的細節,並為網路、磁碟和多核通信提供了通用的 API。它還提供了適用於不同用例的回調和協程風格的狀態管理。

Scylla 的不同領域突顯了可以使用 I/O 的不同方式:

壓縮使用了應用程序級的預讀和後寫,以確保高吞吐量,但由於預期的低命中率而避免應用程序級的緩存(並避免使用冷數據佔滿緩存)

查詢(讀取)使用了應用程序控制的預讀和應用程序級的緩存。應用程序控制的預讀可以防止預讀超時,因為我們事先知道磁碟上數據的邊界。此外,應用程序級的緩存不僅可以緩存從磁碟讀取的數據,還可以緩存從多個文件將數據合併到一起的單一緩存項。

小讀取對齊到 512 位元組的邊界,以減少匯流排數據傳輸和延遲

Seastar I/O 調度器允許我們動態地控制壓縮和查詢(以及其他操作類)的 I/O 速率,以滿足用戶級別協議(SLA)

單獨的 I/O 調度類確保 commitlog 寫入獲得所需的帶寬,不受讀取支配,也不支配讀取

AIO/DIO 是繞過內核從應用程序直接驅動 NVMe 驅動程序的不錯開端。這可能成為未來的 Seastar 功能。

結論

我們在 Linux 上展示了四種不同的方式來執行磁碟 I/O 以及它們各自的利弊。 通過傳統的讀/寫操作可以輕鬆開始,通過 mmap 可以獲得良好的內存性能,但為了實現最佳性能和控制,我們為 Scylla 選擇了非同步 I/O。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 開源中國 的精彩文章:

Spring Framework 5.x 中的新特性
提高代碼可讀性的 10 個技巧
Facebook 是如何進行大規模代碼部署的
src2png——將代碼生成美觀的圖片
不要浪費時間寫完美代碼

TAG:開源中國 |