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