文件系統(tǒng)與磁盤常見優(yōu)化方案和術(shù)語
本文轉(zhuǎn)載自微信公眾號「運維開發(fā)故事」,作者沒有文案的夏老師 。轉(zhuǎn)載本文請聯(lián)系運維開發(fā)故事公眾號。
術(shù)語
文件系統(tǒng)
計算機(jī)的文件系統(tǒng)是一種存儲和組織計算機(jī)數(shù)據(jù)的方法,它使得對其訪問和查找變得容易,文件系統(tǒng)使用文件和樹形目錄的抽象邏輯概念代替了硬盤和光盤等物理設(shè)備使用數(shù)據(jù)塊的概念,用戶使用文件系統(tǒng)來保存數(shù)據(jù)不必關(guān)心數(shù)據(jù)實際保存在硬盤(或者光盤)的地址為多少的數(shù)據(jù)塊上,只需要記住這個文件的所屬目錄和文件名。在寫入新數(shù)據(jù)之前,用戶不必關(guān)心硬盤上的那個塊地址沒有被使用,硬盤上的存儲空間管理(分配和釋放)功能由文件系統(tǒng)自動完成,用戶只需要記住數(shù)據(jù)被寫入到了哪個文件中。
I/O
I/O(英語:Input/Output),即輸入/輸出,通常指數(shù)據(jù)在存儲器(內(nèi)部和外部)或其他周邊設(shè)備之間的輸入和輸出,是信息處理系統(tǒng)(例如計算機(jī))與外部世界(可能是人類或另一信息處理系統(tǒng))之間的通信。輸入是系統(tǒng)接收的信號或數(shù)據(jù),輸出則是從其發(fā)送的信號或數(shù)據(jù)。該術(shù)語也可以用作行動的一部分;到“運行I/O”是運行輸入或輸出的操作。
文件緩存
內(nèi)存上的一塊的一塊區(qū)域,用來緩存文件系統(tǒng)的內(nèi)容。索引節(jié)點緩存,簡稱為 inode,用來記錄文件的元數(shù)據(jù),比如 inode 編號、文件大小、訪問權(quán)限、修改日期、數(shù)據(jù)的位置等。索引節(jié)點和文件一一對應(yīng),它跟文件內(nèi)容一樣,都會被持久化存儲到磁盤中。目錄項,簡稱為 dentry,用來記錄文件的名字、索引節(jié)點指針以及與其他目錄項的關(guān)聯(lián)關(guān)系。多個關(guān)聯(lián)的目錄項,就構(gòu)成了文件系統(tǒng)的目錄結(jié)構(gòu)。不過,不同于索引節(jié)點,目錄項是由內(nèi)核維護(hù)的一個內(nèi)存數(shù)據(jù)結(jié)構(gòu),所以通常也被叫做目錄項緩存。
隨機(jī)I/O與順序I/O
順序IO是指讀寫操作的訪問地址連續(xù)。在順序IO訪問中,HDD所需的磁道搜索時間顯著減少,因為讀/寫磁頭可以以最小的移動訪問下一個塊。數(shù)據(jù)備份和日志記錄等業(yè)務(wù)是順序IO業(yè)務(wù)。隨機(jī)IO是指讀寫操作時間連續(xù),但訪問地址不連續(xù),隨機(jī)分布在磁盤的地址空間中。產(chǎn)生隨機(jī)IO的業(yè)務(wù)有OLTP服務(wù),SQL,即時消息服務(wù)等。
預(yù)讀
Linux文件預(yù)讀算法磁盤I/O性能的發(fā)展遠(yuǎn)遠(yuǎn)滯后于CPU和內(nèi)存,因而成為現(xiàn)代計算機(jī)系統(tǒng)的一個主要瓶頸。預(yù)讀可以有效的減少磁盤的尋道次數(shù)和應(yīng)用程序的I/O等待時間,是改進(jìn)磁盤讀I/O性能的重要優(yōu)化手段之一。本文作者是中國科學(xué)技術(shù)大學(xué)自動化系的博士生,他在1998年開始學(xué)習(xí)Linux,為了優(yōu)化服務(wù)器的性能,他開始嘗試改進(jìn)Linux kernel,并最終重寫了內(nèi)核的文件預(yù)讀部分,這些改進(jìn)被收錄到Linux Kernel 2.6.23及其后續(xù)版本中。預(yù)取算法的涵義和應(yīng)用非常廣泛。它存在于CPU、硬盤、內(nèi)核、應(yīng)用程序以及網(wǎng)絡(luò)的各個層次。預(yù)取有兩種方案:啟發(fā)性的(heuristic prefetching)和知情的(informed prefetching)。前者自動自發(fā)的進(jìn)行預(yù)讀決策,對上層應(yīng)用是透明的,但是對算法的要求較高,存在命中率的問題;后者則簡單的提供API接口,而由上層程序給予明確的預(yù)讀指示。在磁盤這個層次,Linux為我們提供了三個API接口:posix_fadvise(2), readahead(2), madvise(2)。不過真正使用上述預(yù)讀API的應(yīng)用程序并不多見:因為一般情況下,內(nèi)核中的啟發(fā)式算法工作的很好。預(yù)讀(readahead)算法預(yù)測即將訪問的頁面,并提前把它們批量的讀入緩存。它的主要功能和任務(wù)可以用三個關(guān)鍵詞來概括:1、批量,也就是把小I/O聚集為大I/O,以改善磁盤的利用率,提升系統(tǒng)的吞吐量。2、提前,也就是對應(yīng)用程序隱藏磁盤的I/O延遲,以加快程序運行。3、預(yù)測,這是預(yù)讀算法的核心任務(wù)。前兩個功能的達(dá)成都有賴于準(zhǔn)確的預(yù)測能力。當(dāng)前包括Linux、FreeBSD和Solaris等主流操作系統(tǒng)都遵循了一個簡單有效的原則:把讀模式分為隨機(jī)讀和順序讀兩大類,并只對順序讀進(jìn)行預(yù)讀。這一原則相對保守,但是可以保證很高的預(yù)讀命中率,同時有效率/覆蓋率也很好。因為順序讀是最簡單而普遍的,而隨機(jī)讀在內(nèi)核來說也確實是難以預(yù)測的。
寫回緩存
回寫緩存存在有一個與生俱來的潛在問題,即應(yīng)用程序在接到寫完成信號之后可能會進(jìn)行一些其他操作,而在數(shù)據(jù)被真正寫入非易失性介質(zhì)之前系統(tǒng)失效。此時就會導(dǎo)致介質(zhì)上的數(shù)據(jù)與后續(xù)操作不一致性。由于這個問題,良好的回寫緩存實現(xiàn)時要有在系統(tǒng)失效期間(包括電源失效)保護(hù)緩存內(nèi)容,當(dāng)系統(tǒng)重啟時再寫入介質(zhì)的機(jī)制。
吞吐量
磁盤的吞吐量,也就是每秒磁盤 I/O 的流量,即磁盤寫入加上讀出的數(shù)據(jù)的大小。
IOPS
磁盤的 IOPS,也就是在一秒內(nèi),磁盤進(jìn)行多少次 I/O 讀寫。
寫時復(fù)制
寫入時復(fù)制(英語:Copy-on-write,簡稱COW)是一種計算機(jī)程序設(shè)計領(lǐng)域的優(yōu)化策略。其核心思想是,如果有多個調(diào)用者(callers)同時請求相同資源(如內(nèi)存或磁盤上的數(shù)據(jù)存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調(diào)用者試圖修改資源的內(nèi)容時,系統(tǒng)才會真正復(fù)制一份專用副本(private copy)給該調(diào)用者,而其他調(diào)用者所見到的最初的資源仍然保持不變。這過程對其他的調(diào)用者都是透明的(transparently)。此做法主要的優(yōu)點是如果調(diào)用者沒有修改該資源,就不會有副本(private copy)被建立,因此多個調(diào)用者只是讀取操作時可以共享同一份資源。沒有COW之前 第一代Unix系統(tǒng)實現(xiàn)了一種傻瓜式的進(jìn)程創(chuàng)建:當(dāng)執(zhí)行fork系統(tǒng)調(diào)用時,內(nèi)核復(fù)制父進(jìn)程的整個用戶空間并把復(fù)制得到的那一份分配給子進(jìn)程。這種行為是非常耗時的,因為它需要完成以下幾項任務(wù):
- 為子進(jìn)程的頁表分配頁面
- 為子進(jìn)程的頁分配頁面
- 初始化子進(jìn)程的頁表
- 把父進(jìn)程的頁復(fù)制到子進(jìn)程對應(yīng)的頁中
有COW之后
在Linux中,系統(tǒng)調(diào)用fork()創(chuàng)建子進(jìn)程時,并不會立即為子進(jìn)程創(chuàng)建新的物理內(nèi)存空間(邏輯空間當(dāng)然還是保持獨立,只是說兩份邏輯空間一開始映射到同一份物理空間),而是公用父進(jìn)程的物理空間。只有在需要寫入的時候,數(shù)據(jù)才會被復(fù)制,從而使父進(jìn)程、子進(jìn)程擁有各自的副本。也就是說,資源的復(fù)制只有在需要寫入的時候才進(jìn)行,在此之前以只讀方式共享。
零拷貝
技術(shù)是指計算機(jī)執(zhí)行操作時,CPU不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個特定區(qū)域。這種技術(shù)通常用于通過網(wǎng)絡(luò)傳輸文件時節(jié)省CPU周期和內(nèi)存帶寬。實現(xiàn)零復(fù)制的軟件通常依靠基于直接存儲器訪問(DMA)的復(fù)制,以及通過內(nèi)存管理單元(MMU)的內(nèi)存映射。這些功能需要特定硬件的支持,并通常涉及到特定存儲器的對齊。一種較新的方式為使用異構(gòu)系統(tǒng)架構(gòu)(HSA),便于CPU和GPU以及其他處理器傳遞指針。這需要CPU和GPU使用統(tǒng)一地址空間。Linux內(nèi)核通過各個系統(tǒng)調(diào)用支持零復(fù)制,例如sys/socket.h的sendfile、sendfile64以及splice。它們部分在POSIX中指定,因此也存在于BSD內(nèi)核或IBM AIX中,部分則是Linux內(nèi)核API中獨有。
使用率
是指磁盤處理 I/O 的時間百分比。過高的使用率(比如超過 80%),通常意味著磁盤 I/O 存在性能瓶頸。
飽和度
飽和度是指磁盤處理 I/O 的繁忙程度。過高的飽和度,意味著磁盤存在嚴(yán)重的性能瓶頸。當(dāng)飽和度為 100% 時,磁盤無法接受新的 I/O 請求。
響應(yīng)時間
響應(yīng)時間是指 I/O 請求從發(fā)出到收到響應(yīng)的間隔時間。
優(yōu)化方案
當(dāng)然, 想要優(yōu)化 I/O 性能,肯定離不開 Linux 系統(tǒng)的 I/O 棧圖的思路輔助。
應(yīng)用程序優(yōu)化
首先,我們來看一下,從應(yīng)用程序的角度有哪些優(yōu)化 I/O 的思路。應(yīng)用程序處于整個 I/O 棧的最上端,它可以通過系統(tǒng)調(diào)用,來調(diào)整 I/O 模式(如順序還是隨機(jī)、同步還是異步), 同時,它也是 I/O 數(shù)據(jù)的最終來源。在我看來,可以有這么幾種方式來優(yōu)化應(yīng)用程序的 I/O 性能。
第一,可以用追加寫代替隨機(jī)寫,減少尋址開銷,加快 I/O 寫的速度。
第二,可以借助緩存 I/O ,充分利用系統(tǒng)緩存,降低實際 I/O 的次數(shù)。
第三,可以在應(yīng)用程序內(nèi)部構(gòu)建自己的緩存,或者用 Redis 這類外部緩存系統(tǒng)。這樣,一方面,能在應(yīng)用程序內(nèi)部,控制緩存的數(shù)據(jù)和生命周期;另一方面,也能降低其他應(yīng)用程序使用緩存對自身的影響。C 標(biāo)準(zhǔn)庫提供的 fopen、fread 等庫函數(shù),都會利用標(biāo)準(zhǔn)庫的緩存,減少磁盤的操作。而你直接使用 open、read 等系統(tǒng)調(diào)用時,就只能利用操作系統(tǒng)提供的頁緩存和緩沖區(qū)等,而沒有庫函數(shù)的緩存可用。
第四,在需要頻繁讀寫同一塊磁盤空間時,可以用 mmap 代替 read/write,減少內(nèi)存的拷貝次數(shù)。
第五,在需要同步寫的場景中,盡量將寫請求合并,而不是讓每個請求都同步寫入磁盤,即可以用 fsync() 取代 O_SYNC。
第六,在多個應(yīng)用程序共享相同磁盤時,為了保證 I/O 不被某個應(yīng)用完全占用,推薦你使用 cgroups 的 I/O 子系統(tǒng),來限制進(jìn)程 / 進(jìn)程組的 IOPS 以及吞吐量。最后,在使用 CFQ 調(diào)度器時,可以用 ionice 來調(diào)整進(jìn)程的 I/O 調(diào)度優(yōu)先級,特別是提高核心應(yīng)用的 I/O 優(yōu)先級。ionice 支持三個優(yōu)先級類:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 還分別支持 0-7 的級別,數(shù)值越小,則表示優(yōu)先級別越高。
文件系統(tǒng)優(yōu)化
應(yīng)用程序訪問普通文件時,實際是由文件系統(tǒng)間接負(fù)責(zé),文件在磁盤中的讀寫。所以,跟文件系統(tǒng)中相關(guān)的也有很多優(yōu)化 I/O 性能的方式。
第一,你可以根據(jù)實際負(fù)載場景的不同,選擇最適合的文件系統(tǒng)。比如 Ubuntu 默認(rèn)使用 ext4 文件系統(tǒng),而 CentOS 7 默認(rèn)使用 xfs 文件系統(tǒng)。相比于 ext4 ,xfs 支持更大的磁盤分區(qū)和更大的文件數(shù)量,如 xfs 支持大于 16TB 的磁盤。但是 xfs 文件系統(tǒng)的缺點在于無法收縮,而 ext4 則可以。其他對比:
- 初始化模式下,ext4性能并沒有比xfs來得高
- 隨機(jī)讀寫模式下,ext4性能比xfs將近高一倍
- 其他測試模式中,ext4和xfs性能相當(dāng)
在一些對隨機(jī)IO性能要求較高的環(huán)境下,可以嘗試使用ext4,比如數(shù)據(jù)庫,大型圖片后臺存儲等
第二,在選好文件系統(tǒng)后,還可以進(jìn)一步優(yōu)化文件系統(tǒng)的配置選項,包括文件系統(tǒng)的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、掛載選項(如 noatime)等等。比如, 使用 tune2fs 這個工具,可以調(diào)整文件系統(tǒng)的特性(tune2fs 也常用來查看文件系統(tǒng)超級塊的內(nèi)容)。而通過 /etc/fstab ,或者 mount 命令行參數(shù),我們可以調(diào)整文件系統(tǒng)的日志模式和掛載選項等。
第三,可以優(yōu)化文件系統(tǒng)的緩存。
- 比如,你可以優(yōu)化 pdflush 臟頁的刷新頻率(比如設(shè)置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及臟頁的限額(比如調(diào)整 dirty_background_ratio 和 dirty_ratio 等)。
- 再如,你還可以優(yōu)化內(nèi)核回收目錄項緩存和索引節(jié)點緩存的傾向,即調(diào)整 vfs_cache_pressure(/proc/sys/vm/vfs_cache_pressure,默認(rèn)值 100),數(shù)值越大,就表示越容易回收。最后,在不需要持久化時,你還可以用內(nèi)存文件系統(tǒng) tmpfs,以獲得更好的 I/O 性能 。tmpfs 把數(shù)據(jù)直接保存在內(nèi)存中,而不是磁盤中。比如 /dev/shm/ ,就是大多數(shù) Linux 默認(rèn)配置的一個內(nèi)存文件系統(tǒng),它的大小默認(rèn)為總內(nèi)存的一半。
磁盤優(yōu)化
數(shù)據(jù)的持久化存儲,最終還是要落到具體的物理磁盤中,同時,磁盤也是整個 I/O 棧的最底層。從磁盤角度出發(fā),自然也有很多有效的性能優(yōu)化方法。
第一,最簡單有效的優(yōu)化方法,就是換用性能更好的磁盤,比如用 SSD 替代 HDD。
第二,我們可以使用 RAID ,把多塊磁盤組合成一個邏輯磁盤,構(gòu)成冗余獨立磁盤陣列。這樣做既可以提高數(shù)據(jù)的可靠性,又可以提升數(shù)據(jù)的訪問性能。
第三,針對磁盤和應(yīng)用程序 I/O 模式的特征,我們可以選擇最適合的 I/O 調(diào)度算法。比方說,SSD 和虛擬機(jī)中的磁盤,通常用的是 noop 調(diào)度算法。而數(shù)據(jù)庫應(yīng)用,我更推薦使用 deadline 算法。
第四,我們可以對應(yīng)用程序的數(shù)據(jù),進(jìn)行磁盤級別的隔離。比如,我們可以為日志、數(shù)據(jù)庫等 I/O 壓力比較重的應(yīng)用,配置單獨的磁盤。
第五,在順序讀比較多的場景中,我們可以增大磁盤的預(yù)讀數(shù)據(jù),比如,你可以通過下面兩種方法,調(diào)整 /dev/sdb 的預(yù)讀大小。
- 調(diào)整內(nèi)核選項 /sys/block/sdb/queue/read_ahead_kb,默認(rèn)大小是 128 KB,單位為 KB。
- 使用 blockdev 工具設(shè)置,比如 blockdev --setra 8192 /dev/sdb,注意這里的單位是 512B(0.5KB),所以它的數(shù)值總是 read_ahead_kb 的兩倍。
第六,我們可以優(yōu)化內(nèi)核塊設(shè)備 I/O 的選項。比如,可以調(diào)整磁盤隊列的長度 /sys/block/sdb/queue/nr_requests,適當(dāng)增大隊列長度,可以提升磁盤的吞吐量(當(dāng)然也會導(dǎo)致 I/O 延遲增大)。
最后,要注意,磁盤本身出現(xiàn)硬件錯誤,也會導(dǎo)致 I/O 性能急劇下降,所以發(fā)現(xiàn)磁盤性能急劇下降時,你還需要確認(rèn),磁盤本身是不是出現(xiàn)了硬件錯誤。比如,你可以查看 dmesg 中是否有硬件 I/O 故障的日志。還可以使用 badblocks、smartctl 等工具,檢測磁盤的硬件問題,或用 e2fsck 等來檢測文件系統(tǒng)的錯誤。如果發(fā)現(xiàn)問題,你可以使用 fsck 等工具來修復(fù)。