1.Linux 文件系統的工作原理
1.索引節點和目錄項
在 Linux 中一切皆文件。不僅普通的文件和目錄,就連塊設備、套接字、管道等,也都要通過統一的文件系統來管理。
為了方便管理,Linux 文件系統為每個文件都分配兩個數據結構,索引節點(index node)和目錄項(directory entry)。它們主要用來記錄文件的元信息和目錄結構。
- 索引節點,簡稱為 inode,用來記錄文件的元數據,比如 inode 編號、文件大小、訪問權限、修改日期、數據的位置等。索引節點和文件一一對應,它跟文件內容一樣,都會被持久化存儲到磁盤中。所以記住,索引節點同樣占用磁盤空間。
- 目錄項,簡稱為 dentry,用來記錄文件的名字、索引節點指針以及與其他目錄項的關聯關系。多個關聯的目錄項,就構成了文件系統的目錄結構。不過,不同於索引節點,目錄項是由內核維護的一個內存數據結構,所以通常也被叫做目錄項緩存。
2.虛擬文件系統
虛擬文件系統VFS 定義了一組所有文件系統都支持的數據結構和標准接口。這樣,用戶進程和內核中的其他子系統,只需要跟 VFS 提供的統一接口進行交互就可以了,而不需要再關心底層各種文件系統的實現細節。
下面這張圖是Linux 文件系統的架構圖,幫你更好地理解系統調用、VFS、緩存、文件系統以及塊存儲之間的關系。
通過這張圖,你可以看到,在 VFS 的下方,Linux 支持各種各樣的文件系統,如 Ext4、XFS、NFS 等等。按照存儲位置的不同,這些文件系統可以分為三類。
- 第一類是基於磁盤的文件系統,也就是把數據直接存儲在計算機本地掛載的磁盤中。常見的 Ext4、XFS、OverlayFS 等,都是這類文件系統。
- 第二類是基於內存的文件系統,也就是我們常說的虛擬文件系統。這類文件系統,不需要任何磁盤分配存儲空間,但會占用內存。我們經常用到的 /proc 文件系統,其實就是一種最常見的虛擬文件系統。此外,/sys 文件系統也屬於這一類,主要向用戶空間導出層次化的內核對象。
- 第三類是網絡文件系統,也就是用來訪問其他計算機數據的文件系統,比如 NFS、SMB、iSCSI 等。
3.文件系統 I/O
文件讀寫方式的各種差異,導致 I/O 的分類多種多樣。最常見的有,緩沖與非緩沖 I/O、直接與非直接 I/O、阻塞與非阻塞 I/O、同步與異步 I/O 等。 接下來,我們就詳細看這四種分類。
第一種,根據是否利用標准庫緩存,可以把文件 I/O 分為緩沖 I/O 與非緩沖 I/O。
- 緩沖 I/O,是指利用標准庫緩存來加速文件的訪問,而標准庫內部再通過系統調度訪問文件。
- 非緩沖 I/O,是指直接通過系統調用來訪問文件,不再經過標准庫緩存。
無論緩沖 I/O 還是非緩沖 I/O,它們最終還是要經過系統調用來訪問文件。而根據上一節內容,我們知道,系統調用后,還會通過頁緩存,來減少磁盤的 I/O 操作。
第二,根據是否利用操作系統的頁緩存,可以把文件 I/O 分為直接 I/O 與非直接 I/O。
- 直接 I/O,是指跳過操作系統的頁緩存,直接跟文件系統交互來訪問文件。
- 非直接 I/O 正好相反,文件讀寫時,先要經過系統的頁緩存,然后再由內核或額外的系統調用,真正寫入磁盤。
想要實現直接 I/O,需要你在系統調用中,指定 O_DIRECT 標志。如果沒有設置過,默認的是非直接 I/O。
不過要注意,直接 I/O、非直接 I/O,本質上還是和文件系統交互。如果是在數據庫等場景中,你還會看到,跳過文件系統讀寫磁盤的情況,也就是我們通常所說的裸 I/O。
第三,根據應用程序是否阻塞自身運行,可以把文件 I/O 分為阻塞 I/O 和非阻塞 I/O:
- 所謂阻塞 I/O,是指應用程序執行 I/O 操作后,如果沒有獲得響應,就會阻塞當前線程,自然就不能執行其他任務。
- 所謂非阻塞 I/O,是指應用程序執行 I/O 操作后,不會阻塞當前的線程,可以繼續執行其他的任務,隨后再通過輪詢或者事件通知的形式,獲取調用的結果。
比方說,訪問管道或者網絡套接字時,設置 O_NONBLOCK 標志,就表示用非阻塞方式訪問;而如果不做任何設置,默認的就是阻塞訪問。
第四,根據是否等待響應結果,可以把文件 I/O 分為同步和異步 I/O:
- 所謂同步 I/O,是指應用程序執行 I/O 操作后,要一直等到整個 I/O 完成后,才能獲得 I/O 響應。
- 所謂異步 I/O,是指應用程序執行 I/O 操作后,不用等待完成和完成后的響應,而是繼續執行就可以。等到這次 I/O 完成后,響應會用事件通知的方式,告訴應用程序。
4.性能觀測
容量
對文件系統來說,最常見的一個問題就是空間不足。當然,你可能本身就知道,用 df 命令,就能查看文件系統的磁盤空間使用情況。比如:
df -h /dev/sda1
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 20G
6
.4G 13G
35
% /
|
不過有時候,明明你碰到了空間不足的問題,可是用 df 查看磁盤空間后,卻發現剩余空間還有很多。這是怎么回事呢?
#給 df 命令加上 -i 參數,查看索引節點的使用情況
df -i /dev/sda1
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1
1310720
167343
1143377
13
% /
|
索引節點的容量,(也就是 Inode 個數)是在格式化磁盤時設定好的,一般由格式化工具自動生成。當你發現索引節點空間不足,但磁盤空間充足時,很可能就是過多小文件導致的。所以,一般來說,刪除這些小文件,或者把它們移動到索引節點充足的其他磁盤中,就可以解決這個問題。
緩存
在前面 Cache 案例中,我已經介紹過,可以用 free 或 vmstat,來觀察頁緩存的大小。free 輸出的 Cache,是頁緩存和可回收 Slab 緩存的和,你可以從 /proc/meminfo ,直接得到它們的大小:
cat /proc/meminfo |grep -E
"SReclaimable|Cached"
Cached:
431680
kB
SwapCached:
27780
kB
SReclaimable:
88540
kB
|
文件系統中的目錄項和索引節點緩存,又該如何觀察呢?
比如,運行下面的命令,你就可以得到,所有目錄項和各種文件系統索引節點的緩存情況:
cat /proc/slabinfo | grep -E
'^#|dentry|inode'
# name : tunables : slabdata
ovl_inode
282
282
688
47
8
: tunables
0
0
0
: slabdata
6
6
0
mqueue_inode_cache
68
68
960
34
8
: tunables
0
0
0
: slabdata
2
2
0
fuse_inode
78
78
832
39
8
: tunables
0
0
0
: slabdata
2
2
0
ecryptfs_inode_cache
0
0
1024
32
8
: tunables
0
0
0
: slabdata
0
0
0
fat_inode_cache
0
0
744
44
8
: tunables
0
0
0
: slabdata
0
0
0
squashfs_inode_cache
11523
11638
704
46
8
: tunables
0
0
0
: slabdata
253
253
0
ext4_inode_cache
23845
27600
1088
30
8
: tunables
0
0
0
: slabdata
920
920
0
hugetlbfs_inode_cache
102
102
632
51
8
: tunables
0
0
0
: slabdata
2
2
0
sock_inode_cache
3990
4278
704
46
8
: tunables
0
0
0
: slabdata
93
93
0
shmem_inode_cache
2307
2668
712
46
8
: tunables
0
0
0
: slabdata
58
58
0
proc_inode_cache
4852
5232
680
48
8
: tunables
0
0
0
: slabdata
109
109
0
inode_cache
22067
23956
608
53
8
: tunables
0
0
0
: slabdata
452
452
0
dentry
68912
88074
192
42
2
: tunables
0
0
0
: slabdata
2097
2097
0
|
這個界面中,dentry 行表示目錄項緩存,inode_cache 行,表示 VFS 索引節點緩存,其余的則是各種文件系統的索引節點緩存。
在實際性能分析中,我們更常使用 slabtop ,來找到占用內存最多的緩存類型。
slabtop
|
2.Linux 磁盤 IO 的工作原理
1.磁盤
磁盤是可以持久化存儲的設備,根據存儲介質的不同,常見磁盤可以分為兩類:機械磁盤和固態磁盤。
- 第一類,機械磁盤,也稱為硬盤驅動器(Hard Disk Driver),通常縮寫為 HDD。機械磁盤主要由盤片和讀寫磁頭組成,數據就存儲在盤片的環狀磁道中。在讀寫數據前,需要移動讀寫磁頭,定位到數據所在的磁道,然后才能訪問數據。
- 第二類,固態磁盤(Solid State Disk),通常縮寫為 SSD,由固態電子元器件組成。固態磁盤不需要磁道尋址,所以,不管是連續 I/O,還是隨機 I/O 的性能,都比機械磁盤要好得多。
其實,無論機械磁盤,還是固態磁盤,相同磁盤的隨機 I/O 都要比連續 I/O 慢很多,原因也很明顯。
- 對機械磁盤來說,由於隨機 I/O 需要更多的磁頭尋道和盤片旋轉,它的性能自然要比連續 I/O 慢。
- 而對固態磁盤來說,雖然它的隨機性能比機械硬盤好很多,但同樣存在“先擦除再寫入”的限制。隨機讀寫會導致大量的垃圾回收,所以相對應的,隨機 I/O 的性能比起連續 I/O 來,也還是差了很多。
- 此外,連續 I/O 還可以通過預讀的方式,來減少 I/O 請求的次數,這也是其性能優異的一個原因。很多性能優化的方案,也都會從這個角度出發,來優化 I/O 性能。
2.通用塊層
通用塊層,其實是處在文件系統和磁盤驅動中間的一個塊設備抽象層。它主要有兩個功能 。
- 第一個功能跟虛擬文件系統的功能類似。向上,為文件系統和應用程序,提供訪問塊設備的標准接口;向下,把各種異構的磁盤設備抽象為統一的塊設備,並提供統一框架來管理這些設備的驅動程序。
- 第二個功能,通用塊層還會給文件系統和應用程序發來的 I/O 請求排隊,並通過重新排序、請求合並等方式,提高磁盤讀寫的效率。
3.I/O 棧
我們可以把 Linux 存儲系統的 I/O 棧,由上到下分為三個層次,分別是文件系統層、通用塊層和設備層。
存儲系統 I/O 的工作原理:
- 文件系統層,包括虛擬文件系統和其他各種文件系統的具體實現。它為上層的應用程序,提供標准的文件訪問接口;對下會通過通用塊層,來存儲和管理磁盤數據。
- 通用塊層,包括塊設備 I/O 隊列和 I/O 調度器。它會對文件系統的 I/O 請求進行排隊,再通過重新排序和請求合並,然后才要發送給下一級的設備層。
- 設備層,包括存儲設備和相應的驅動程序,負責最終物理設備的 I/O 操作。
4.磁盤性能指標
說到磁盤性能的衡量標准,必須要提到五個常見指標,也就是我們經常用到的,使用率、飽和度、IOPS、吞吐量以及響應時間等。這五個指標,是衡量磁盤性能的基本指標。
- 使用率,是指磁盤處理 I/O 的時間百分比。過高的使用率(比如超過 80%),通常意味着磁盤 I/O 存在性能瓶頸。
- 飽和度,是指磁盤處理 I/O 的繁忙程度。過高的飽和度,意味着磁盤存在嚴重的性能瓶頸。當飽和度為 100% 時,磁盤無法接受新的 I/O 請求。
- IOPS(Input/Output Per Second),是指每秒的 I/O 請求數。
- 吞吐量,是指每秒的 I/O 請求大小。
- 響應時間,是指 I/O 請求從發出到收到響應的間隔時間。
這里要注意的是,使用率只考慮有沒有 I/O,而不考慮 I/O 的大小。換句話說,當使用率是 100% 的時候,磁盤依然有可能接受新的 I/O 請求。
4.1磁盤 I/O 觀測
第一個要觀測的,是每塊磁盤的使用情況。
iostat 的輸出界面如下。
-d -x 表示顯示所有磁盤 I/O 的指標
$ iostat -d -x
1
Linux
4.18
.
0
-
15
-generic (ubuntu)
02
/
26
/
2019
_x86_64_ (
2
CPU)
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
loop0
0.00
0.00
0.01
0.00
0.00
0.00
0.00
0.00
1.63
0.00
0.00
3.73
0.00
1.85
0.00
loop1
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.33
0.00
0.00
2.53
0.00
0.57
0.00
loop2
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
3.12
0.00
0.00
1.91
0.00
2.83
0.00
loop3
0.05
0.00
0.07
0.00
0.00
0.00
0.00
0.00
0.71
0.00
0.00
1.50
0.00
0.24
0.00
loop4
0.02
0.00
0.03
0.00
0.00
0.00
0.00
0.00
0.18
0.00
0.00
1.35
0.00
0.26
0.00
loop5
0.02
0.00
0.04
0.00
0.00
0.00
0.00
0.00
0.24
0.00
0.00
2.23
0.00
0.29
0.00
loop6
0.00
0.00
0.01
0.00
0.00
0.00
0.00
0.00
1.21
0.00
0.00
4.07
0.00
1.58
0.00
loop7
0.68
0.00
0.70
0.00
0.00
0.00
0.00
0.00
0.69
0.00
0.00
1.03
0.00
0.05
0.00
sda
74.81
2.35
53938.75
142.55
4.59
10.91
5.78
82.30
7.01
34.69
0.69
721.03
60.77
0.26
2.04
loop8
0.01
0.00
0.03
0.00
0.00
0.00
0.00
0.00
0.20
0.00
0.00
3.06
0.00
0.21
0.00
loop9
0.01
0.00
0.02
0.00
0.00
0.00
0.00
0.00
0.09
0.00
0.00
1.69
0.00
0.13
0.00
|
iostat指標解讀:
這些指標中,你要注意:
- %util ,就是我們前面提到的磁盤 I/O 使用率;
- r/s+ w/s ,就是 IOPS;
- rkB/s+wkB/s ,就是吞吐量;
- r_await+w_await ,就是響應時間。
在觀測指標時,也別忘了結合請求的大小( rareq-sz 和 wareq-sz)一起分析。
4.2.進程 I/O 觀測
上面提到的 iostat 只提供磁盤整體的 I/O 性能數據,缺點在於,並不能知道具體是哪些進程在進行磁盤讀寫。要觀察進程的 I/O 情況,你還可以使用 pidstat 和 iotop 這兩個工具。
$ pidstat -d
1
13
:
39
:
51
UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
13
:
39
:
52
102
916
0.00
4.00
0.00
0
rsyslogd
|
從 pidstat 的輸出你能看到,它可以實時查看每個進程的 I/O 情況,包括下面這些內容。
- 用戶 ID(UID)和進程 ID(PID) 。
- 每秒讀取的數據大小(kB_rd/s) ,單位是 KB。
- 每秒發出的寫請求數據大小(kB_wr/s) ,單位是 KB。
- 每秒取消的寫請求數據大小(kB_ccwr/s) ,單位是 KB。
- 塊 I/O 延遲(iodelay),包括等待同步塊 I/O 和換入塊 I/O 結束的時間,單位是時鍾周期。
除了可以用 pidstat 實時查看,根據 I/O 大小對進程排序,也是性能分析中一個常用的方法。這一點,我推薦另一個工具, iotop。它是一個類似於 top 的工具,你可以按照 I/O 大小對進程排序,然后找到 I/O 較大的那些進程。
iotop 的輸出如下所示:
從這個輸出,你可以看到,前兩行分別表示,進程的磁盤讀寫大小總數和磁盤真實的讀寫大小總數。因為緩存、緩沖區、I/O 合並等因素的影響,它們可能並不相等。