1. <th id="orwrz"></th>
        <tbody id="orwrz"><pre id="orwrz"></pre></tbody>
      2. <rp id="orwrz"><object id="orwrz"></object></rp>
        首頁 運維干貨介紹下Linux I/O 那些事兒

        介紹下Linux I/O 那些事兒

        運維派隸屬馬哥教育旗下專業運維社區,是國內成立最早的IT運維技術社區,歡迎關注公眾號:yunweipai
        領取學習更多免費Linux云計算、Python、Docker、K8s教程關注公眾號:馬哥linux運維

        介紹下Linux I/O 那些事兒插圖

        介紹 Linux IO 的一些基本原理。

        作者:arraywang,騰訊 CSIG

        我們先看一張圖:

        介紹下Linux I/O 那些事兒插圖1

        這張圖大體上描述了 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-- 1 root    root    1, 2 4月  25 11:03 bnod // 塊設備文件  
        crw-r--r-- 1 root    root    1, 2 4月  25 11:04 cnod // 符號設備文件  
        drwxr-xr-x 2 wrn3552 wrn3552    6 4月  25 11:01 dir // 目錄  
        -rw-r--r-- 1 wrn3552 wrn3552    0 4月  25 11:01 file // 普通文件  
        prw-r--r-- 1 root    root       0 4月  25 11:04 pipeline // 有名管道  
        srwxr-xr-x 1 root    root       0 4月  25 11:06 socket.sock // socket文件  
        lrwxrwxrwx 1 root    root       4 4月  25 11:04 softlink -> file // 軟連接  
        -rw-r--r-- 2 wrn3552 wrn3552 0 4月  25 11:07 hardlink // 硬鏈接(本質也是普通文件)  
        

        Linux 文件系統設計了兩個數據結構來管理這些不同種類的文件:

        • inode(index node):索引節點
        • dentry(directory entry):目錄項
        inode 和 dentry

        inode

        inode 是用來記錄文件的 metadata,所謂 metadata 在 Wikipedia 上的描述是 data of data,其實指的就是文件的各種屬性,比如 inode 編號、文件大小、訪問權限、修改日期、數據的位置等。

        wrn3552@novadev:~/playground$ stat file  
          文件:file  
          大?。?               塊: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。

        文件是如何存儲在磁盤上的

        介紹下Linux I/O 那些事兒插圖2

        這里有張圖解釋了文件是如何存儲在磁盤上的,首先,磁盤再進行文件系統格式化的時候,會分出來 3 個區:

        1. Superblock
        2. inode blocks
        3. data blocks

        (其實還有 boot block,可能會包含一些 bootstrap 代碼,在機器啟動的時候被讀到,這里忽略)其中 inode blocks 放的都是每個文件的 inode,data blocks 里放的是每個文件的內容數據。這里關注一下 superblock,它包含了整個文件系統的 metadata,具體有:

        1. inode/data block 總量、使用量、剩余量

        2. 文件系統的格式,屬主等等各種屬性

        superblock 對于文件系統來說非常重要,如果 superblock 損壞了,文件系統就掛載不了了,相應的文件也沒辦法讀寫。既然 superblock 這么重要,那肯定不能只有一份,壞了就沒了,它在系統中是有很多副本的,在 superblock 損壞的時候,可以使用 fsck(File System Check and repair)來恢復?;氐缴厦娴哪菑垐D,可以很清晰地看到文件的各種屬性和文件的數據是如何存儲在磁盤上的:

        1. dentry 里包含了文件的名字、目錄結構、inode 指針
        2. inode 指針指向文件特定的 inode(存在 inode blocks 里)
        3. 每個 inode 又指向 data blocks 里具體的 logical block,這里的 logical block 存的就是文件具體的數據

        這里解釋一下什么是 logical block:

        1. 對于不同存儲介質的磁盤,都有最小的讀寫單元
          /sys/block/sda/queue/physical_block_size
        2. HDD 叫做 sector(扇區),SSD 叫做 page(頁面)
        3. 對于 hdd 來說,每個 sector 大小 512Bytes
        4. 對于 SSD 來說每個 page 大小不等(和 cell 類型有關),經典的大小是 4KB
        5. 但是 Linux 覺得按照存儲介質的最小讀寫單元來進行讀寫可能會有效率問題,所以支持在文件系統格式化的時候指定 block size 的大小,一般是把幾個 physical_block 拼起來就成了一個 logical block /sys/block/sda/queue/logical_block_size

        6. 理論上應該是 logical_block_size >= physical_block_size,但是有時候我們會看到 physical_block_size = 4K,logical_block_size = 512B 情況,其實這是因為磁盤上做了一層 512B 的仿真(emulation)(詳情可參考 512e 和 4Kn

        ZFS

        這里簡單介紹一個廣泛應用的文件系統 ZFS,一些數據庫應用也會用到 ZFS,先看一張 zfs 的層級結構圖:

        介紹下Linux I/O 那些事兒插圖3

        這是一張從底向上的圖:

        1. 將若干物理設備 disk 組成一個虛擬設備 vdev(同時,disk 也是一種 vdev)
        2. 再將若干個虛擬設備 vdev 加到一個 zpool 里
        3. 在 zpool 的基礎上創建 zfs 并掛載(zvol 可以先不看,我們沒有用到)

        ZFS 的一些操作

        創建 zpool

        root@:~ # zpool create tank raidz /dev/ada1 /dev/ada2 /dev/ada3 raidz /dev/ada4 /dev/ada5 /dev/ada6  
        root@:~ # zpool list tank  
        NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT  
        tank     11G   824K  11.0G        -         -     0%     0%  1.00x  ONLINE  -  
        root@:~ # zpool status tank  
          pool: tank  
         state: ONLINE  
          scan: none requested  
        config:  
        
                NAME        STATE     READ WRITE CKSUM  
                tank        ONLINE       0     0     0  
                  raidz1-0  ONLINE       0     0     0  
                    ada1    ONLINE       0     0     0  
                    ada2    ONLINE       0     0     0  
                    ada3    ONLINE       0     0     0  
                  raidz1-1  ONLINE       0     0     0  
                    ada4    ONLINE       0     0     0  
                    ada5    ONLINE       0     0     0  
                    ada6    ONLINE       0     0     0  
        
        • 創建了一個名為 tank 的 zpool
        • 這里的 raidz 同 RAID5

        除了 raidz 還支持其他方案:

        介紹下Linux I/O 那些事兒插圖4

        創建 zfs

        `root@:~ # zfs create -o mountpoint=/mnt/srev tank/srev  
        root@:~ # df -h tank/srev  
        Filesystem    Size    Used   Avail Capacity  Mounted on  
        tank/srev     7.1G    117K    7.1G     0%    /mnt/srev  
        
        • 創建了一個 zfs,掛載到了 /mnt/srev
        • 這里沒有指定 zfs 的 quota,創建的 zfs 大小即 zpool 大小

        對 zfs 設置 quota

        root@:~ # zfs set quota=1G tank/srev  
        root@:~ # df -h tank/srev  
        Filesystem    Size    Used   Avail Capacity  Mounted on  
        tank/srev     1.0G    118K    1.0G     0%    /mnt/srev  
        

        ZFS 特性

        Pool 存儲

        上面的層級圖和操作步驟可以看到 zfs 是基于 zpool 創建的,zpool 可以動態擴容意味著存儲空間也可以動態擴容,而且可以創建多個文件系統,文件系統共享完整的 zpool 空間無需預分配。

        事務文件系統

        zfs 的寫操作是事務的,意味著要么就沒寫,要么就寫成功了,不會像其他文件系統那樣,應用打開了文件,寫入還沒保存的時候斷電,導致文件為空。zfs 保證寫操作事務采用的是 copy on write 的方式:

        介紹下Linux I/O 那些事兒插圖5

        • 當 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 個鏈表:

        1. LRU list:最近最多使用的頁面,存具體數據
        2. LFU list:最近最常使用的頁面,存具體數據
        3. Ghost list for LRU:最近從 LRU 表淘汰下來的頁面信息,不存具體數據,只存頁面信息
        4. Ghost list for LFU:最近從 LFU 表淘汰下來的頁面信息,不存具體數據,只存頁面信息

        ARC 工作流程大致如下:

        1. LRU list 和 LFU list 填充和淘汰過程和標準算法一樣
        2. 當一個頁面從 LRU list 淘汰下來時,這個頁面的信息會放到 LRU ghost 表中
        3. 如果這個頁面一直沒被再次引用到,那么這個頁面的信息最終也會在 LRU ghost 表中被淘汰掉
        4. 如果這個頁面在 LRU ghost 表中未被淘汰的時候,被再一次訪問了,這時候會引起一次幽靈(phantom)命中
        5. phantom 命中的時候,事實上還是要把數據從磁盤第一次放緩存
        6. 但是這時候系統知道剛剛被 LRU 表淘汰的頁面又被訪問到了,說明 LRU list 太小了,這時它會把 LRU list 長度加一,LFU 長度減一
        7. 對于 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---- 1 root disk 8,  0 Apr 25 15:53 /dev/sda  
        brw-rw---- 1 root disk 8,  1 Apr 25 15:53 /dev/sda1  
        brw-rw---- 1 root disk 8, 10 Apr 25 15:53 /dev/sda10  
        brw-rw---- 1 root disk 8,  2 Apr 25 15:53 /dev/sda2  
        brw-rw---- 1 root disk 8,  5 Apr 25 15:53 /dev/sda5  
        brw-rw---- 1 root disk 8,  6 Apr 25 15:53 /dev/sda6  
        brw-rw---- 1 root disk 8,  7 Apr 25 15:53 /dev/sda7  
        brw-rw---- 1 root disk 8,  8 Apr 25 15:53 /dev/sda8  
        brw-rw---- 1 root disk 8,  9 Apr 25 15:53 /dev/sda9
        
        • 這些 sda 磁盤主設備號都是 8,表示它是一個 sd 類型的塊設備
        • 次設備號 0-10 表示這些不同 sd 塊設備的編號

        Generic Block Layer

        圖片

        和 VFS 類似,為了對上層屏蔽不同塊設備的差異,內核在文件系統和塊設備之前抽象了一個 Generic Block Layer(通用塊層),有時候一些人也會把下面的 I/O 調度層并到通用塊層里表述。

        這兩層主要做兩件事:

        1. 跟 VFS 的功能類似。向上,為文件系統和應用程序,提供訪問塊設備的標準接口;向下,把各種異構的磁盤設備抽象為統一的塊設備,并提供統一框架來管理這些設備的驅動程序
        2. 對 I/O 請求進行調度,通過重新排序、合并等方式,提高磁盤讀寫效率

        下圖是一個完整的 I/O 棧全景圖:

        介紹下Linux I/O 那些事兒插圖7

        可以看到中間的 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:

        性能指標

        一般來說 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/O
        • iotop:類似 top,按 I/O 大小對進程排序

        本文鏈接:http://www.abandonstatusquo.com/40573.html

        網友評論comments

        發表評論

        您的電子郵箱地址不會被公開。

        暫無評論

        Copyright ? 2012-2022 YUNWEIPAI.COM - 運維派 京ICP備16064699號-6
        掃二維碼
        掃二維碼
        返回頂部
        十分钟免费观看视频高清下载