Linux I/O
Linux I/O 那些事兒 https://mp.weixin.qq.com/s/diKfeu1-Lr4ZA5Ky_66TZg
Linux I/O 那些事兒
介紹 Linux IO 的一些基本原理。
作者:arraywang,騰訊 CSIG
我們先看一張圖:
這張圖大體上描述了 Linux 系統上,應用程式對磁碟上的檔案進行讀寫時,從上到下經歷了哪些事情。
這篇文章就以這張圖為基礎,介紹 Linux 在 I/O 上做了哪些事情。
檔案系統
什麼是檔案系統
檔案系統,本身是對儲存裝置上的檔案,進行組織管理的機制。組織方式不同,就會形成不同的檔案系統。比如常見的 Ext4、XFS、ZFS 以及網路檔案系統 NFS 等等。
但是不同型別的檔案系統標準和介面可能各有差異,我們在做應用開發的時候卻很少關心繫統呼叫以下的具體實現,大部分時候都是直接系統呼叫open
,read
,write
,close
來實現應用程式的功能,不會再去關注我們具體用了什麼檔案系統(UFS、XFS、Ext4、ZFS),磁碟是什麼介面(IDE、SCSI,SAS,SATA 等),磁碟是什麼儲存介質(HDD、SSD)
應用開發者之所以這麼爽,各種複雜細節都不用管直接調介面,是因為核心為我們做了大量的有技術含量的髒活累活。開始的那張圖看到 Linux 在各種不同的檔案系統之上,虛擬了一個 VFS,目的就是統一各種不同檔案系統的標準和介面,讓開發者可以使用相同的系統呼叫來使用不同的檔案系統。
檔案系統如何工作(VFS)
Linux 系統下的檔案
在 Linux 中一切皆檔案。不僅普通的檔案和目錄,就連塊裝置、套接字、管道等,也都要通過統一的檔案系統來管理。
用ls-l命令看最前面的字元可以看到這個檔案是什麼型別
brw-r--r--1rootroot1,24月2511:03bnod//塊裝置檔案
crw-r--r--1rootroot1,24月2511:04cnod//符號裝置檔案
drwxr-xr-x2wrn3552wrn355264月2511:01dir//目錄
-rw-r--r--1wrn3552wrn355204月2511:01file//普通檔案
prw-r--r--1rootroot04月2511:04pipeline//有名管道
srwxr-xr-x1rootroot04月2511:06socket.sock//socket檔案
lrwxrwxrwx1rootroot44月2511:04softlink->file//軟連線
-rw-r--r--2wrn3552wrn355204月2511:07hardlink//硬連結(本質也是普通檔案)
Linux 檔案系統設計了兩個資料結構來管理這些不同種類的檔案:
- inode(index node):索引節點
- dentry(directory entry):目錄項
inode 和 dentry
inode
inode 是用來記錄檔案的 metadata,所謂 metadata 在 Wikipedia 上的描述是 data of data,其實指的就是檔案的各種屬性,比如 inode 編號、檔案大小、訪問許可權、修改日期、資料的位置等。
wrn3552@novadev:~/playground$statfile
檔案:file
大小:0塊:0 IO 塊:4096 普通空檔案
裝置:fe21h/65057d Inode:32828 硬連結:2
許可權:(0644/-rw-r--r--) Uid:( 3041/ wrn3552) Gid:( 3041/ wrn3552)
最近訪問:2021-04-25 11:07:59.603745534 +0800
最近更改:2021-04-25 11:07:59.603745534 +0800
最近改動:2021-04-25 11:08:04.739848692 +0800
建立時間:-
inode 和檔案一一對應,它跟檔案內容一樣,都會被持久化儲存到磁碟中。所以,inode 同樣佔用磁碟空間,只不過相對於檔案來說它大小固定且大小不算大。
dentry
dentry 用來記錄檔案的名字、inode 指標以及與其他 dentry 的關聯關係。
wrn3552@novadev:~/playground$tree
.
├──dir
│└──file_in_dir
├──file
└──hardlink
- 檔案的名字:像 dir、file、hardlink、file_in_dir 這些名字是記錄在 dentry 裡的
- inode 指標:就是指向這個檔案的 inode
- 與其他 dentry 的關聯關係:其實就是每個檔案的層級關係,哪個檔案在哪個檔案下面,構成了檔案系統的目錄結構
不同於 inode,dentry 是由核心維護的一個記憶體資料結構,所以通常也被叫做 dentry cache。
檔案是如何儲存在磁碟上的
這裡有張圖解釋了檔案是如何儲存在磁碟上的,首先,磁碟再進行檔案系統格式化的時候,會分出來 3 個區:
- Superblock
- inode blocks
- data blocks
(其實還有 boot block,可能會包含一些 bootstrap 程式碼,在機器啟動的時候被讀到,這裡忽略)其中 inode blocks 放的都是每個檔案的 inode,data blocks 裡放的是每個檔案的內容資料。這裡關注一下 superblock,它包含了整個檔案系統的 metadata,具體有:
- inode/data block 總量、使用量、剩餘量
- 檔案系統的格式,屬主等等各種屬性
superblock 對於檔案系統來說非常重要,如果 superblock 損壞了,檔案系統就掛載不了了,相應的檔案也沒辦法讀寫。既然 superblock 這麼重要,那肯定不能只有一份,壞了就沒了,它在系統中是有很多副本的,在 superblock 損壞的時候,可以使用fsck
(File System Check and repair)來恢復。回到上面的那張圖,可以很清晰地看到檔案的各種屬性和檔案的資料是如何儲存在磁碟上的:
- dentry 裡包含了檔案的名字、目錄結構、inode 指標
- inode 指標指向檔案特定的 inode(存在 inode blocks 裡)
- 每個 inode 又指向 data blocks 裡具體的 logical block,這裡的 logical block 存的就是檔案具體的資料
這裡解釋一下什麼是 logical block:
- 對於不同儲存介質的磁碟,都有最小的讀寫單元
/sys/block/sda/queue/physical_block_size
- HDD 叫做 sector(扇區),SSD 叫做 page(頁面)
- 對於 hdd 來說,每個 sector 大小 512Bytes
- 對於 SSD 來說每個 page 大小不等(和 cell 型別有關),經典的大小是 4KB
- 但是 Linux 覺得按照儲存介質的最小讀寫單元來進行讀寫可能會有效率問題,所以支援在檔案系統格式化的時候指定 block size 的大小,一般是把幾個 physical_block 拼起來就成了一個 logical block
/sys/block/sda/queue/logical_block_size
- 理論上應該是 logical_block_size >= physical_block_size,但是有時候我們會看到 physical_block_size = 4K,logical_block_size = 512B 情況,其實這是因為磁碟上做了一層 512B 的模擬(emulation)(詳情可參考512e 和 4Kn)
ZFS
這裡簡單介紹一個廣泛應用的檔案系統 ZFS,一些資料庫應用也會用到 ZFS,先看一張 zfs 的層級結構圖:
這是一張從底向上的圖:
- 將若干物理裝置 disk 組成一個虛擬裝置 vdev(同時,disk 也是一種 vdev)
- 再將若干個虛擬裝置 vdev 加到一個 zpool 裡
- 在 zpool 的基礎上建立 zfs 並掛載(zvol 可以先不看,我們沒有用到)
ZFS 的一些操作
建立 zpool
root@:~#zpoolcreatetankraidz/dev/ada1/dev/ada2/dev/ada3raidz/dev/ada4/dev/ada5/dev/ada6
root@:~#zpoollisttank
NAMESIZEALLOCFREECKPOINTEXPANDSZFRAGCAPDEDUPHEALTHALTROOT
tank11G824K11.0G--0%0%1.00xONLINE-
root@:~#zpoolstatustank
pool:tank
state:ONLINE
scan:nonerequested
config:
NAMESTATEREADWRITECKSUM
tankONLINE000
raidz1-0ONLINE000
ada1ONLINE000
ada2ONLINE000
ada3ONLINE000
raidz1-1ONLINE000
ada4ONLINE000
ada5ONLINE000
ada6ONLINE000
- 建立了一個名為 tank 的 zpool
- 這裡的 raidz 同 RAID5
除了 raidz 還支援其他方案:
建立 zfs
root@:~#zfscreate-omountpoint=/mnt/srevtank/srev
root@:~#df-htank/srev
FilesystemSizeUsedAvailCapacityMountedon
tank/srev7.1G117K7.1G0%/mnt/srev
- 建立了一個 zfs,掛載到了 /mnt/srev
- 這裡沒有指定 zfs 的 quota,建立的 zfs 大小即 zpool 大小
對 zfs 設定 quota
root@:~#zfssetquota=1Gtank/srev
root@:~#df-htank/srev
FilesystemSizeUsedAvailCapacityMountedon
tank/srev1.0G118K1.0G0%/mnt/srev
ZFS 特性
Pool 儲存
上面的層級圖和操作步驟可以看到 zfs 是基於 zpool 建立的,zpool 可以動態擴容意味著儲存空間也可以動態擴容,而且可以建立多個檔案系統,檔案系統共享完整的 zpool 空間無需預分配。
事務檔案系統
zfs 的寫操作是事務的,意味著要麼就沒寫,要麼就寫成功了,不會像其他檔案系統那樣,應用打開了檔案,寫入還沒儲存的時候斷電,導致檔案為空。zfs 保證寫操作事務採用的是 copy on write 的方式:
- 當 block B 有修改變成 B1 的時候,普通的檔案系統會直接在 block B 原地進行修改變成 B1
- zfs 則會再另一個地方寫 B1,然後再在後面安全的時候對原來的 B 進行回收
- 這樣結果就不會出現 B 被開啟而寫失敗的情況,大不了就是 B1 沒寫成功
這個特性讓 zfs 在斷電後不需要執行 fsck 來檢查磁碟中是否存在寫操作失敗需要恢復的情況,大大提升了應用的可用性。
ARC 快取
ZFS 中的 ARC(Adjustable Replacement Cache) 讀快取淘汰演算法,是基於 IBM 的 ARP(Adaptive Replacement Cache) 演化而來。在一些檔案系統中實現的標準 LRU 演算法其實是有缺陷的:比如複製大檔案之類的線性大量 I/O 操作,導致快取失效率猛增(大量檔案只讀一次,放到記憶體不會被再讀,坐等淘汰)。
另外,快取可以根據時間來進行優化(LRU,最近最多使用),也可以根據頻率進行優化(LFU,最近最常使用),這兩種方法各有優劣,但是沒辦法適應所有場景。
ARC 的設計就是嘗試在 LRU 和 LFU 之間找到一個平衡,根據當前的 I/O workload 來調整用 LRU 多一點還是 LFU 多一點。
ARC 定義了 4 個連結串列:
- LRU list:最近最多使用的頁面,存具體資料
- LFU list:最近最常使用的頁面,存具體資料
- Ghost list for LRU:最近從 LRU 表淘汰下來的頁面資訊,不存具體資料,只存頁面資訊
- Ghost list for LFU:最近從 LFU 表淘汰下來的頁面資訊,不存具體資料,只存頁面資訊
ARC 工作流程大致如下:
- LRU list 和 LFU list 填充和淘汰過程和標準演算法一樣
- 當一個頁面從 LRU list 淘汰下來時,這個頁面的資訊會放到 LRU ghost 表中
- 如果這個頁面一直沒被再次引用到,那麼這個頁面的資訊最終也會在 LRU ghost 表中被淘汰掉
- 如果這個頁面在 LRU ghost 表中未被淘汰的時候,被再一次訪問了,這時候會引起一次幽靈(phantom)命中
- phantom 命中的時候,事實上還是要把資料從磁碟第一次放快取
- 但是這時候系統知道剛剛被 LRU 表淘汰的頁面又被訪問到了,說明 LRU list 太小了,這時它會把 LRU list 長度加一,LFU 長度減一
- 對於 LFU 的過程也與上述過程類似
ZFS 參考資料
關於 ZFS 詳細介紹可以參考:
- 這篇文章
磁碟型別
磁碟根據不同的分類方式,有各種不一樣的型別。
磁碟的儲存介質
根據磁碟的儲存介質可以分兩類(大家都很熟悉):
- HDD(機械硬碟)
- SSD(固態硬碟)
磁碟的介面
根據磁碟介面分類:
- IDE (Integrated Drive Electronics)
- SCSI (Small Computer System Interface)
- SAS (Serial Attached SCSI)
- SATA (Serial ATA)
- ...
不同的介面,往往分配不同的裝置名稱。比如, IDE 裝置會分配一個 hd 字首的裝置名,SCSI 和 SATA 裝置會分配一個 sd 字首的裝置名。如果是多塊同類型的磁碟,就會按照 a、b、c 等的字母順序來編號。
Linux 對磁碟的管理
其實在 Linux 中,磁碟實際上是作為一個塊裝置來管理的,也就是以塊為單位讀寫資料,並且支援隨機讀寫。每個塊裝置都會被賦予兩個裝置號,分別是主、次裝置號。主裝置號用在驅動程式中,用來區分裝置型別;而次裝置號則是用來給多個同類裝置編號。
g18-"299"on~#ls-l/dev/sda*
brw-rw----1rootdisk8,0Apr2515:53/dev/sda
brw-rw----1rootdisk8,1Apr2515:53/dev/sda1
brw-rw----1rootdisk8,10Apr2515:53/dev/sda10
brw-rw----1rootdisk8,2Apr2515:53/dev/sda2
brw-rw----1rootdisk8,5Apr2515:53/dev/sda5
brw-rw----1rootdisk8,6Apr2515:53/dev/sda6
brw-rw----1rootdisk8,7Apr2515:53/dev/sda7
brw-rw----1rootdisk8,8Apr2515:53/dev/sda8
brw-rw----1rootdisk8,9Apr2515:53/dev/sda9
- 這些 sda 磁碟主裝置號都是 8,表示它是一個 sd 型別的塊裝置
- 次裝置號 0-10 表示這些不同 sd 塊裝置的編號
Generic Block Layer
和 VFS 類似,為了對上層遮蔽不同塊裝置的差異,核心在檔案系統和塊裝置之前抽象了一個 Generic Block Layer(通用塊層),有時候一些人也會把下面的 I/O 排程層併到通用塊層裡表述。
這兩層主要做兩件事:
- 跟 VFS 的功能類似。向上,為檔案系統和應用程式,提供訪問塊裝置的標準介面;向下,把各種異構的磁碟裝置抽象為統一的塊裝置,並提供統一框架來管理這些裝置的驅動程式
- 對 I/O 請求進行排程,通過重新排序、合併等方式,提高磁碟讀寫效率
下圖是一個完整的 I/O 棧全景圖:
可以看到中間的 Block Layer 其實就是 Generic Block Layer,在圖中可以看到 Block Layer 的 I/O 排程分為兩類,分別表示單佇列和多佇列的排程:
- I/O scheduler
- blkmq
I/O 排程
老版本的核心裡只支援單佇列的 I/O scheduler,在 3.16 版本的核心開始支援多佇列 blkmq,這裡介紹幾種經典的 I/O 排程策略。
單佇列 I/O scheduler:
- NOOP:事實上是個 FIFO 的佇列,只做基本的請求合併
- CFQ:Completely Fair Queueing,完全公平排程器,給每個程序維護一個 I/O 排程佇列,按照時間片來均勻分佈每個程序 I/O 請求,
- DeadLine:為讀和寫請求建立不同的 I/O 佇列,確保達到 deadline 的請求被優先處理
多佇列 blkmq:
- bfq:Budget Fair Queueing,也是公平排程器,不過不是按時間片來分配,而是按請求的扇區數量(頻寬)
- kyber:維護兩個佇列(同步/讀、非同步/寫),同時嚴格限制發到這兩個佇列的請求數以保證相應時間
- mq-deadline:多佇列版本的 deadline
- 具體各種 I/O 排程策略可以參考IOSchedulers
- 關於 blkmq 可以參考Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) Details
- 多佇列排程可以參考Block layer introduction part 2: the request layer
效能指標
一般來說 I/O 效能指標有這幾個:
- 使用率:ioutil,指的是磁碟處理 I/O 的時間百分比,ioutil 只看有沒有 I/O 請求,不看 I/O 請求的大小。ioutil 越高表示一直都有 I/O 請求,不代表磁碟無法響應新的 I/O 請求
- IOPS:每秒的 I/O 請求數
- 吞吐量/頻寬:每秒的 I/O 請求大小,通常是 MB/s 或者 GB/s 為單位
- 響應時間:I/O 請求發出到收到響應的時間
- 飽和度:指的是磁碟處理 I/O 的繁忙程度。這個指標比較玄學,沒有直接的資料可以表示,一般是根據平均佇列請求長度或者響應時間跟基準測試的結果進行對比來估算
(在做基準測試時,還會分順序/隨機、讀/寫進行排列組合分別去測 IOPS 和頻寬)
上面的指標除了飽和度外,其他都可以在監控系統中看到。Linux 也提供了一些命令來輸出不同維度的 I/O 狀態:
iostat -d -x
:看各個裝置的 I/O 狀態,資料來源/proc/diskstats
pidstat -d
:看近處的 I/Oiotop
:類似 top,按 I/O 大小對程序排序
介紹 Linux IO 的一些基本原理。
作者:arraywang,騰訊 CSIG
我們先看一張圖:
這張圖大體上描述了 Linux 系統上,應用程式對磁碟上的檔案進行讀寫時,從上到下經歷了哪些事情。
這篇文章就以這張圖為基礎,介紹 Linux 在 I/O 上做了哪些事情。
檔案系統
什麼是檔案系統
檔案系統,本身是對儲存裝置上的檔案,進行組織管理的機制。組織方式不同,就會形成不同的檔案系統。比如常見的 Ext4、XFS、ZFS 以及網路檔案系統 NFS 等等。
但是不同型別的檔案系統標準和介面可能各有差異,我們在做應用開發的時候卻很少關心繫統呼叫以下的具體實現,大部分時候都是直接系統呼叫open
,read
,write
,close
來實現應用程式的功能,不會再去關注我們具體用了什麼檔案系統(UFS、XFS、Ext4、ZFS),磁碟是什麼介面(IDE、SCSI,SAS,SATA 等),磁碟是什麼儲存介質(HDD、SSD)
應用開發者之所以這麼爽,各種複雜細節都不用管直接調介面,是因為核心為我們做了大量的有技術含量的髒活累活。開始的那張圖看到 Linux 在各種不同的檔案系統之上,虛擬了一個 VFS,目的就是統一各種不同檔案系統的標準和介面,讓開發者可以使用相同的系統呼叫來使用不同的檔案系統。
檔案系統如何工作(VFS)
Linux 系統下的檔案
在 Linux 中一切皆檔案。不僅普通的檔案和目錄,就連塊裝置、套接字、管道等,也都要通過統一的檔案系統來管理。
用ls-l命令看最前面的字元可以看到這個檔案是什麼型別
brw-r--r--1rootroot1,24月2511:03bnod//塊裝置檔案
crw-r--r--1rootroot1,24月2511:04cnod//符號裝置檔案
drwxr-xr-x2wrn3552wrn355264月2511:01dir//目錄
-rw-r--r--1wrn3552wrn355204月2511:01file//普通檔案
prw-r--r--1rootroot04月2511:04pipeline//有名管道
srwxr-xr-x1rootroot04月2511:06socket.sock//socket檔案
lrwxrwxrwx1rootroot44月2511:04softlink->file//軟連線
-rw-r--r--2wrn3552wrn355204月2511:07hardlink//硬連結(本質也是普通檔案)
Linux 檔案系統設計了兩個資料結構來管理這些不同種類的檔案:
- inode(index node):索引節點
- dentry(directory entry):目錄項
inode 和 dentry
inode
inode 是用來記錄檔案的 metadata,所謂 metadata 在 Wikipedia 上的描述是 data of data,其實指的就是檔案的各種屬性,比如 inode 編號、檔案大小、訪問許可權、修改日期、資料的位置等。
wrn3552@novadev:~/playground$statfile
檔案:file
大小:0塊:0 IO 塊:4096 普通空檔案
裝置:fe21h/65057d Inode:32828 硬連結:2
許可權:(0644/-rw-r--r--) Uid:( 3041/ wrn3552) Gid:( 3041/ wrn3552)
最近訪問:2021-04-25 11:07:59.603745534 +0800
最近更改:2021-04-25 11:07:59.603745534 +0800
最近改動:2021-04-25 11:08:04.739848692 +0800
建立時間:-
inode 和檔案一一對應,它跟檔案內容一樣,都會被持久化儲存到磁碟中。所以,inode 同樣佔用磁碟空間,只不過相對於檔案來說它大小固定且大小不算大。
dentry
dentry 用來記錄檔案的名字、inode 指標以及與其他 dentry 的關聯關係。
wrn3552@novadev:~/playground$tree
.
├──dir
│└──file_in_dir
├──file
└──hardlink
- 檔案的名字:像 dir、file、hardlink、file_in_dir 這些名字是記錄在 dentry 裡的
- inode 指標:就是指向這個檔案的 inode
- 與其他 dentry 的關聯關係:其實就是每個檔案的層級關係,哪個檔案在哪個檔案下面,構成了檔案系統的目錄結構
不同於 inode,dentry 是由核心維護的一個記憶體資料結構,所以通常也被叫做 dentry cache。
檔案是如何儲存在磁碟上的
這裡有張圖解釋了檔案是如何儲存在磁碟上的,首先,磁碟再進行檔案系統格式化的時候,會分出來 3 個區:
- Superblock
- inode blocks
- data blocks
(其實還有 boot block,可能會包含一些 bootstrap 程式碼,在機器啟動的時候被讀到,這裡忽略)其中 inode blocks 放的都是每個檔案的 inode,data blocks 裡放的是每個檔案的內容資料。這裡關注一下 superblock,它包含了整個檔案系統的 metadata,具體有:
- inode/data block 總量、使用量、剩餘量
- 檔案系統的格式,屬主等等各種屬性
superblock 對於檔案系統來說非常重要,如果 superblock 損壞了,檔案系統就掛載不了了,相應的檔案也沒辦法讀寫。既然 superblock 這麼重要,那肯定不能只有一份,壞了就沒了,它在系統中是有很多副本的,在 superblock 損壞的時候,可以使用fsck
(File System Check and repair)來恢復。回到上面的那張圖,可以很清晰地看到檔案的各種屬性和檔案的資料是如何儲存在磁碟上的:
- dentry 裡包含了檔案的名字、目錄結構、inode 指標
- inode 指標指向檔案特定的 inode(存在 inode blocks 裡)
- 每個 inode 又指向 data blocks 裡具體的 logical block,這裡的 logical block 存的就是檔案具體的資料
這裡解釋一下什麼是 logical block:
- 對於不同儲存介質的磁碟,都有最小的讀寫單元
/sys/block/sda/queue/physical_block_size
- HDD 叫做 sector(扇區),SSD 叫做 page(頁面)
- 對於 hdd 來說,每個 sector 大小 512Bytes
- 對於 SSD 來說每個 page 大小不等(和 cell 型別有關),經典的大小是 4KB
- 但是 Linux 覺得按照儲存介質的最小讀寫單元來進行讀寫可能會有效率問題,所以支援在檔案系統格式化的時候指定 block size 的大小,一般是把幾個 physical_block 拼起來就成了一個 logical block
/sys/block/sda/queue/logical_block_size
- 理論上應該是 logical_block_size >= physical_block_size,但是有時候我們會看到 physical_block_size = 4K,logical_block_size = 512B 情況,其實這是因為磁碟上做了一層 512B 的模擬(emulation)(詳情可參考512e 和 4Kn)
ZFS
這裡簡單介紹一個廣泛應用的檔案系統 ZFS,一些資料庫應用也會用到 ZFS,先看一張 zfs 的層級結構圖:
這是一張從底向上的圖:
- 將若干物理裝置 disk 組成一個虛擬裝置 vdev(同時,disk 也是一種 vdev)
- 再將若干個虛擬裝置 vdev 加到一個 zpool 裡
- 在 zpool 的基礎上建立 zfs 並掛載(zvol 可以先不看,我們沒有用到)
ZFS 的一些操作
建立 zpool
root@:~#zpoolcreatetankraidz/dev/ada1/dev/ada2/dev/ada3raidz/dev/ada4/dev/ada5/dev/ada6
root@:~#zpoollisttank
NAMESIZEALLOCFREECKPOINTEXPANDSZFRAGCAPDEDUPHEALTHALTROOT
tank11G824K11.0G--0%0%1.00xONLINE-
root@:~#zpoolstatustank
pool:tank
state:ONLINE
scan:nonerequested
config:
NAMESTATEREADWRITECKSUM
tankONLINE000
raidz1-0ONLINE000
ada1ONLINE000
ada2ONLINE000
ada3ONLINE000
raidz1-1ONLINE000
ada4ONLINE000
ada5ONLINE000
ada6ONLINE000
- 建立了一個名為 tank 的 zpool
- 這裡的 raidz 同 RAID5
除了 raidz 還支援其他方案:
建立 zfs
root@:~#zfscreate-omountpoint=/mnt/srevtank/srev
root@:~#df-htank/srev
FilesystemSizeUsedAvailCapacityMountedon
tank/srev7.1G117K7.1G0%/mnt/srev
- 建立了一個 zfs,掛載到了 /mnt/srev
- 這裡沒有指定 zfs 的 quota,建立的 zfs 大小即 zpool 大小
對 zfs 設定 quota
root@:~#zfssetquota=1Gtank/srev
root@:~#df-htank/srev
FilesystemSizeUsedAvailCapacityMountedon
tank/srev1.0G118K1.0G0%/mnt/srev
ZFS 特性
Pool 儲存
上面的層級圖和操作步驟可以看到 zfs 是基於 zpool 建立的,zpool 可以動態擴容意味著儲存空間也可以動態擴容,而且可以建立多個檔案系統,檔案系統共享完整的 zpool 空間無需預分配。
事務檔案系統
zfs 的寫操作是事務的,意味著要麼就沒寫,要麼就寫成功了,不會像其他檔案系統那樣,應用打開了檔案,寫入還沒儲存的時候斷電,導致檔案為空。zfs 保證寫操作事務採用的是 copy on write 的方式:
- 當 block B 有修改變成 B1 的時候,普通的檔案系統會直接在 block B 原地進行修改變成 B1
- zfs 則會再另一個地方寫 B1,然後再在後面安全的時候對原來的 B 進行回收
- 這樣結果就不會出現 B 被開啟而寫失敗的情況,大不了就是 B1 沒寫成功
這個特性讓 zfs 在斷電後不需要執行 fsck 來檢查磁碟中是否存在寫操作失敗需要恢復的情況,大大提升了應用的可用性。
ARC 快取
ZFS 中的 ARC(Adjustable Replacement Cache) 讀快取淘汰演算法,是基於 IBM 的 ARP(Adaptive Replacement Cache) 演化而來。在一些檔案系統中實現的標準 LRU 演算法其實是有缺陷的:比如複製大檔案之類的線性大量 I/O 操作,導致快取失效率猛增(大量檔案只讀一次,放到記憶體不會被再讀,坐等淘汰)。
另外,快取可以根據時間來進行優化(LRU,最近最多使用),也可以根據頻率進行優化(LFU,最近最常使用),這兩種方法各有優劣,但是沒辦法適應所有場景。
ARC 的設計就是嘗試在 LRU 和 LFU 之間找到一個平衡,根據當前的 I/O workload 來調整用 LRU 多一點還是 LFU 多一點。
ARC 定義了 4 個連結串列:
- LRU list:最近最多使用的頁面,存具體資料
- LFU list:最近最常使用的頁面,存具體資料
- Ghost list for LRU:最近從 LRU 表淘汰下來的頁面資訊,不存具體資料,只存頁面資訊
- Ghost list for LFU:最近從 LFU 表淘汰下來的頁面資訊,不存具體資料,只存頁面資訊
ARC 工作流程大致如下:
- LRU list 和 LFU list 填充和淘汰過程和標準演算法一樣
- 當一個頁面從 LRU list 淘汰下來時,這個頁面的資訊會放到 LRU ghost 表中
- 如果這個頁面一直沒被再次引用到,那麼這個頁面的資訊最終也會在 LRU ghost 表中被淘汰掉
- 如果這個頁面在 LRU ghost 表中未被淘汰的時候,被再一次訪問了,這時候會引起一次幽靈(phantom)命中
- phantom 命中的時候,事實上還是要把資料從磁碟第一次放快取
- 但是這時候系統知道剛剛被 LRU 表淘汰的頁面又被訪問到了,說明 LRU list 太小了,這時它會把 LRU list 長度加一,LFU 長度減一
- 對於 LFU 的過程也與上述過程類似
ZFS 參考資料
關於 ZFS 詳細介紹可以參考:
- 這篇文章
磁碟型別
磁碟根據不同的分類方式,有各種不一樣的型別。
磁碟的儲存介質
根據磁碟的儲存介質可以分兩類(大家都很熟悉):
- HDD(機械硬碟)
- SSD(固態硬碟)
磁碟的介面
根據磁碟介面分類:
- IDE (Integrated Drive Electronics)
- SCSI (Small Computer System Interface)
- SAS (Serial Attached SCSI)
- SATA (Serial ATA)
- ...
不同的介面,往往分配不同的裝置名稱。比如, IDE 裝置會分配一個 hd 字首的裝置名,SCSI 和 SATA 裝置會分配一個 sd 字首的裝置名。如果是多塊同類型的磁碟,就會按照 a、b、c 等的字母順序來編號。
Linux 對磁碟的管理
其實在 Linux 中,磁碟實際上是作為一個塊裝置來管理的,也就是以塊為單位讀寫資料,並且支援隨機讀寫。每個塊裝置都會被賦予兩個裝置號,分別是主、次裝置號。主裝置號用在驅動程式中,用來區分裝置型別;而次裝置號則是用來給多個同類裝置編號。
g18-"299"on~#ls-l/dev/sda*
brw-rw----1rootdisk8,0Apr2515:53/dev/sda
brw-rw----1rootdisk8,1Apr2515:53/dev/sda1
brw-rw----1rootdisk8,10Apr2515:53/dev/sda10
brw-rw----1rootdisk8,2Apr2515:53/dev/sda2
brw-rw----1rootdisk8,5Apr2515:53/dev/sda5
brw-rw----1rootdisk8,6Apr2515:53/dev/sda6
brw-rw----1rootdisk8,7Apr2515:53/dev/sda7
brw-rw----1rootdisk8,8Apr2515:53/dev/sda8
brw-rw----1rootdisk8,9Apr2515:53/dev/sda9
- 這些 sda 磁碟主裝置號都是 8,表示它是一個 sd 型別的塊裝置
- 次裝置號 0-10 表示這些不同 sd 塊裝置的編號
Generic Block Layer
和 VFS 類似,為了對上層遮蔽不同塊裝置的差異,核心在檔案系統和塊裝置之前抽象了一個 Generic Block Layer(通用塊層),有時候一些人也會把下面的 I/O 排程層併到通用塊層裡表述。
這兩層主要做兩件事:
- 跟 VFS 的功能類似。向上,為檔案系統和應用程式,提供訪問塊裝置的標準介面;向下,把各種異構的磁碟裝置抽象為統一的塊裝置,並提供統一框架來管理這些裝置的驅動程式
- 對 I/O 請求進行排程,通過重新排序、合併等方式,提高磁碟讀寫效率
下圖是一個完整的 I/O 棧全景圖:
可以看到中間的 Block Layer 其實就是 Generic Block Layer,在圖中可以看到 Block Layer 的 I/O 排程分為兩類,分別表示單佇列和多佇列的排程:
- I/O scheduler
- blkmq
I/O 排程
老版本的核心裡只支援單佇列的 I/O scheduler,在 3.16 版本的核心開始支援多佇列 blkmq,這裡介紹幾種經典的 I/O 排程策略。
單佇列 I/O scheduler:
- NOOP:事實上是個 FIFO 的佇列,只做基本的請求合併
- CFQ:Completely Fair Queueing,完全公平排程器,給每個程序維護一個 I/O 排程佇列,按照時間片來均勻分佈每個程序 I/O 請求,
- DeadLine:為讀和寫請求建立不同的 I/O 佇列,確保達到 deadline 的請求被優先處理
多佇列 blkmq:
- bfq:Budget Fair Queueing,也是公平排程器,不過不是按時間片來分配,而是按請求的扇區數量(頻寬)
- kyber:維護兩個佇列(同步/讀、非同步/寫),同時嚴格限制發到這兩個佇列的請求數以保證相應時間
- mq-deadline:多佇列版本的 deadline
- 具體各種 I/O 排程策略可以參考IOSchedulers
- 關於 blkmq 可以參考Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) Details
- 多佇列排程可以參考Block layer introduction part 2: the request layer
效能指標
一般來說 I/O 效能指標有這幾個:
- 使用率:ioutil,指的是磁碟處理 I/O 的時間百分比,ioutil 只看有沒有 I/O 請求,不看 I/O 請求的大小。ioutil 越高表示一直都有 I/O 請求,不代表磁碟無法響應新的 I/O 請求
- IOPS:每秒的 I/O 請求數
- 吞吐量/頻寬:每秒的 I/O 請求大小,通常是 MB/s 或者 GB/s 為單位
- 響應時間:I/O 請求發出到收到響應的時間
- 飽和度:指的是磁碟處理 I/O 的繁忙程度。這個指標比較玄學,沒有直接的資料可以表示,一般是根據平均佇列請求長度或者響應時間跟基準測試的結果進行對比來估算
(在做基準測試時,還會分順序/隨機、讀/寫進行排列組合分別去測 IOPS 和頻寬)
上面的指標除了飽和度外,其他都可以在監控系統中看到。Linux 也提供了一些命令來輸出不同維度的 I/O 狀態:
iostat -d -x
:看各個裝置的 I/O 狀態,資料來源/proc/diskstats
pidstat -d
:看近處的 I/Oiotop
:類似 top,按 I/O 大小對程序排序