存儲大師班 | ZFS存儲池塊管理與事務模型


ZFS History

file

  1. ZFS 的誕生:ZFS 由 Sun 存儲部門的 CTO 、研究員 Jeff Bonwick 帶領團隊開發。開發於 2001 年,作為 Sun Microsystems Solaris 操作系統的一部分。

    • 帶領開發 ZFS 的是 Jeff Bonwick。他首先有了對 ZFS 整體架構的構思,然后游說 Sun 高層,親自組建起了 ZFS 開發團隊。

    • 招募了當時剛從大學畢業的 Matt Ahrens,又從 Solaris 的 Storage Team 抽調了 UFS 部分的負責人 Mark Shellenbaum 和 Mark Maybee 來開發 ZFS。

  2. ZFS 的開源:在 2005 年,Sun 開源 Solaris 的大部分,包括 ZFS 開源;開源項目 OpenSolaris。

  3. illumos 項目成立,OpenZFS:

    • 在 2010 年,Sun 被 Oracle 收購,ZFS 成為 Oracle 的注冊商標。Oracle 停止為 OpenSolaris 和 ZFS 項目提供更新的源代碼,三分之二的 ZFS 核心開發者決定離開 Oracle 公司。
    • illumos 項目的成立,維護已經存在的開源的 Solaris 代碼,並且在 2013 年成立 OpenZFS 以配合 ZFS 開源的發展。
  4. ZFS on Linux:2013 年,Linux 上 ZFS 的第一個穩定版本,繼續發展。

ZFS Overview

file

ZFS(Zettabyte File System)是一個跨時代的文件系統,被稱為最后一個單機文件系統,已經被證實可以移植到多種 OS 平台上。它擁有如下特性:

  1. 完全兼容 posix 語義(ZPL: ZFS POSIX Layer)。

  2. 提供邏輯卷功能(ZVOL: ZFS Volumes)。

  3. 提交完善的管理功能:通過 libzfs 的工具,經過 ioctl 發送管理命令到 zfs kernel module。

  4. 提供接近無限的存儲空間:將物理存儲設備進行池化管理,文件系統建立在設備存儲池上。當文件系統空間不夠,可以將物理設備動態添加存儲池中。存儲池上的單個文件系統極限值:

    • 支持 2^{48} 個快照。

    • 支持 2^{48} 個文件。

    • 單個文件最大 16EB。

    事務模型 + COW:

    file

  5. 事務模型和 COW 是強綁定的關系。

  • COW(Copy On Write):對文件的數據不是原地直接修改,而是拷貝更新的方式,數據塊的內容更新不存在中間狀態。

  • 事務:保證了對文件的一組操作都是原子性的,並且每個事務之間是相互獨立的。所有文件的同一個事務 ID 的集合打包成一個事務組,進行從 file 到 root 的全局的拓撲的更新。

  • 事務 TX 和事務組 TXG 的關系(在事務模型章節詳細介紹):

    • XG:在同一個事務 ID(TXG-num)下的 TX 構成一個事務組 TXG

    • 當一個 TXG 中的被需要改的數據達到一個閾值(臟數據閾值),或者周期到了,發生一次事務組的輪轉,在這個事務組的 sync 結束之后,生成一個全新的 root 的拓撲樹(layout)。

  1. 端到端的數據安全性:

file
file

  • 256 位的 Checksum,Checksum 的數據存儲在父親節點上。

  • 形成一棵自驗證的 Merkle Tree, 當 ZFS 在 load 數據時會進行校驗。

  • 它在負責讀取數據的時候會自動和 256 位校驗碼進行校驗,會主動發現這種 Silent Data Corruption:

    • 通過相應的 Mirror 硬盤或者通過 RAID-Z 陣列中其他硬盤得到正確的數據返回給上層應用,並且同時自動修復原硬盤的 Corruption Data。

    • 數據恢復也可以給通過讀取有效副本的方式進行修復:比如像 ZFS 的 spacemap 數據會存儲多份。

  1. 支持快照和克隆
  • 由於 ZFS 采用的 COW 機制,每次的更新 new 數據均不會破壞磁盤上已有的 old 數據。

  • ZFS 可以根據需要保留old 數據。

  • 這是實現快照功能的基礎,實際的快照功能實現是選擇一個 TXG-num 作為快照點,將 old root 下拓撲的保存起來,不去釋放被更新的數據塊地址。

  • 當然實際的快照功能要復雜的多,這里不做贅述。

  1. 支持數據去重功能

  2. 支持數據壓縮功能

存儲池

存儲池 + 塊管理

大多數單機文件系統是物理設備強綁定的,一旦在某個物理設備上格式化文件系統之后,該文件系統的可用空間和物理設備大小息息相關。但是 ZFS 將實際的物理設備和文件系統進行隔離,文件系統是建立在存儲池上,存儲池上的文件系統共用存儲池的物理空間,同時可以動態添加物理設備到存儲池中,以增加存儲池的可用空間。

file

  1. 存儲池中的設備管理是一種樹形結構進行管理的,如下圖所示:

file

	使用如下命令創建一個存儲池 tank
	zpool create -f tank sdc mirror sdd sde raidz1 sdf sdg sdh raidz2 sdi sdj sdk sdl

	邏輯虛擬設備為 VDEV1、VDEV2、VDEV3、VDEV4
	VDEV1 對應的物理設備為:sdc
	VDEV2 對應的物理設備為:sdd、sde組成的RAID1
	VDEV1 對應的物理設備為:sdf、sdg、sdh組成RAIDZ1
	VDEV1 對應的物理設備為:sdi、sdj、sdk、sdl組成的RAIDZ2
  1. Label 磁盤結構:
  • 在 ZFS 中直接的分配空間的是虛擬設備 VDEV 去完成的,每個 VDEV 下的 physical devices 都存在如下的 Label 信息,在物理設備的首尾兩個,共四個。

  • 更新方式是兩階段更新的:在一個 TXG 中 sync 的結束階段先更新 L0,L2;如果成功再更新 L1 和 L3。

  • 這樣更新和布局的好處是:無論何時掉電,總會有一個 Label 是可用的。

  • Name/Vaule 記錄的就是當前的 VDEV 下的物理設備的拓撲關系

file

  • sdc 的 Label 信息

      [root@ks0 ~]# zdb -l /dev/sdc1
      ------------------------------------
      LABEL 0
      ------------------------------------
      	 version: 5000
      	 name: 'tank'
      	 state: 0
      	 txg: 4
      	 pool_guid: 2896170471310910827
      	 errata: 0
      	 hostname: 'ks0'
      	 top_guid: 2751819047198290687   //vdev guid
      	 guid: 2751819047198290687       //phyical dev guid
      	 vdev_children: 4              //總共4個vdevs
      	 vdev_tree:
      			 type: 'disk'
      			 id: 0                       //vdev id編號          
      			 guid: 2751819047198290687   //vdev guid
      			 path: '/dev/sdc1'
      			 whole_disk: 1
      			 metaslab_array: 148         //vdev的metaslab的個數
      			 metaslab_shift: 27          //vdev的metaslab大小
      			 ashift: 9                   //metaslab分最小分配單元512B
      			 asize: 21459632128          //vdev的大小
      			 is_log: 0
      			 create_txg: 4
      	 features_for_read:
      			 com.delphix:hole_birth
      			 com.delphix:embedded_data
      	 labels = 0 1 2 3
    
  • sdd 和 sdc 的 Label 信息

      [root@ks0 ~]# zdb -l /dev/sdd1         | [root@ks0 ~]# zdb -l /dev/sde1  
      ------------------------------------   | --------------------------------  
      LABEL 0                                | LABEL 0  
      ------------------------------------   | --------------------------------
      	 version: 5000                      | version: 5000  
      	 name: 'tank'                       | name: 'tank'  
      	 state: 0                           | state: 0  
      	 txg: 4                             | txg: 4  
      	 pool_guid: 2896170471310910827     | pool_guid: 2896170471310910827  
      	 errata: 0                          | errata: 0  
      	 hostname: 'ks0'                    | hostname: 'ks0'  
      	 top_guid: 14326320160136866584     | top_guid: 14326320160136866584  
      	 guid: 14834175139806304144         | guid: 4487740773939268111  
      	 vdev_children: 4                   | vdev_children: 4  
      	 vdev_tree:                         | vdev_tree:  
      			 type: 'mirror'                 | type: 'mirror'  
      			 id: 1                          | id: 1  
      			 guid: 14326320160136866584     | guid: 14326320160136866584  
      			 metaslab_array: 146            | metaslab_array: 146  
      			 metaslab_shift: 25             | metaslab_shift: 25  
      			 ashift: 9                      | ashift: 9  
      			 asize: 5353504768              | asize: 5353504768  
      			 is_log: 0                      | is_log: 0  
      			 create_txg: 4                  | create_txg: 4  
      			 children[0]:                   | children[0]:  
      					 type: 'disk'               | type: 'disk'  
      					 id: 0                      | id: 0  
      					 guid: 14834175139806304144 | guid: 14834175139806304144  
      					 path: '/dev/sdd1'          | path: '/dev/sdd1'  
      					 whole_disk: 1              | whole_disk: 1  
      					 create_txg: 4              | create_txg: 4  
      			 children[1]:                   | children[1]:  
      					 type: 'disk'               | type: 'disk'  
      					 id: 1                      | id: 1  
      					 guid: 4487740773939268111  | guid: 4487740773939268111  
      					 path: '/dev/sde1'          | path: '/dev/sde1'  
      					 whole_disk: 1              | whole_disk: 1  
      					 create_txg: 4              | create_txg: 4  
      	 features_for_read:                 | features_for_read:  
      			 com.delphix:hole_birth         | com.delphix:hole_birth  
      			 com.delphix:embedded_data      | com.delphix:embedded_data  
      	 labels = 0 1 2 3                   | labels = 0 1 2 3
    

塊管理

  1. 傳統方式:

談到存儲池,也免不了需要介紹塊管理機制,ZFS 的塊管理機制是不同於其他的單機文件系統的管理方式。首先需要介紹一下傳統的幾種塊管理方式:

  • 位圖管理:最常用的空間管理方法是位圖。最小空間單元狀態(分配或者空閑)用一個 bit 位表示。

file

  • 列如:4TB 磁盤,最小分配單元 4KB 需要 4KKKK}/4K/8=256MB;但是隨着空間的增大比如 324T 的空間,這個位圖的大小占用的內存空間就變得無法忽視了,並且每次空間的申請和釋放都是要持久化位圖信息到物理設備上,導致大量的磁盤 IO 操作,存在寫放大問題。

  • 解決方法:分而治之,使用多級管理的方式,比如:將 4TB 的空間分為多組,以位圖組的方式進行管理,以二級甚至多級的方式管理位圖都是可以的。如果有位圖信息需要寫入磁盤,只需要更新有改變的位圖組信息到物理設備上,磁盤 IO 次數大大減少。

  • 存在的問題:如果刪除文件,該文件的空間分布在所有的位圖組上,導致更新位圖組的磁盤 IO 的並沒有減少;位圖管理方式無法避免隨機釋放的帶來的大量的磁盤 IO 問題。

  • B*樹:另外一個常用的管理空閑空間的方式使用 B-tree 管理,葉子節點存儲實際的空閑的的偏移和大小。

file

  • 優點:分配連續的空間效率很高。

  • 存在的問題:刪除大文件時釋放空間同樣會帶來大量的隨機寫,需要更新大量的葉子節點信息,同樣存在寫放大的問題。

  1. ZFS 的管理方式

file

一種全新的空間管理方式:將空間的分配和釋放行為作為 log 存入到磁盤中,而不是將空間的狀態寫入到磁盤。內存中維護的是可用空間的 range-tree< AVL tree >,item 是 offset/size 。周期性的將分配和釋放的記錄寫入到磁盤中。
當系統重新啟動的時候,在內存中重做全部的 allocation 和 free 的記錄,那么可以在內存中構建可用空間的 range-tree。

file

  • 如果有很多分配釋放被相互抵消了,會對之前累計寫入的 log 進行一次精簡。使用可用空間的 range-tree 得到一個分配空間的 range-tree(condense tree),作為 allocation 的記錄寫入到磁盤中。最極端的情況是分配空間的 range-tree 沒有 item(全部空間可用),這個時候需要記錄的 log 大小就是 0(真實情況是即使沒有任何業務分配空間,最少占用一個兩個 data block,一個是 spacemap header,一個記錄 header 的使用的 record)。

  • 或者空間使用的很多之后,也會進行 log 的精簡。最極端的情況是分配空間的 range-tree 可能只有一個 item(空間被全部使用完了),此時的 log 大小是一個 item 的大小。

  • ZFS 將每個 VDEV 的空間進行單獨管理,每個 VDEV 按照按照一定大小進行分段管理管理,每個段稱之為 metaslab ,在內存中使用 metaslab 的 mstree 管理可用空間,分配釋放空間的 log 通過 spacemap 進行記錄。

  • spacemap 使用的空間也會記錄在 spacemap 的內容中。

  • 由於 ZFS 中將 spacemap 也視作為一個文件的,所以更新的方式也是 COW 的,那就會存在每次 sync 中更新 spacemap 都會產生空間分配,那么 spacemap 的更新的怎么結束?ZFS 在更新 spacemap 的過程中取巧:spacemap 在一次 TXG-sync 邏輯中可能會更新多次,第一次更新是 COW 的,以后的更新都是 rewrite(Not COW)的;這樣更新的好處是:

  • COW 保證舊的 spacemap layout(拓撲)是有效的,如果更新的過程配新的地址,而是在第一次 sync 分配的地址上追加的空間 record。

事務模型

file

  • TX 步驟:

    • 更新文件的數據之前,需要將文件的 layout load 到 memory 中,

    • 將更新操作和 TX-ID 綁定起來 < TX-ID 是 TXG-num,依次遞增>,更新 memory 中數據塊的內容。

    • 在 zio pipeline 流程中,分配空間,並寫入物理設備。

    • 將 dnode 標記為 dirty,綁定相關的 TXG 上,讓 TXG-sync 線程邏輯去 COW 更新 dnode 的 indirect block 和 header block。

    • 通知綁定的 TXG(相同TXG值)將 pending 事務計數減去 1。

  • TXG 步驟:

    • TXG 等待所有的 pending TX 完成,此時文件的 data block 內容都已經落盤。

    • 在 sync 線程中,開始更新文件的 indirect block ,將最新的 data block 地址和 checksum 寫入到 indirect 中,為 indirect 和 header 分配空間,一次更新內存中 indirect 和 header,然后寫入到磁盤。

    • 將在這個 TXG 中分配和釋放操作,作為 record 寫入到 spacemap 中 <持久化 metaslab 的分配釋放記錄的結構>;繼續向上更新 metaclass 的信息到磁盤中,直到更新到 uber。

    • 然后兩階段提交 Label中 uber 的信息,uber 中記錄是最新的文件的系統 root 地址。

file

  • root 中記錄 zfs 最新的名字空間的 root 的地址。

  • root 中記錄了最新的空間管理的 root 的地址。

  • root 中記錄最新的 intend log 的 root 的地址。

  • 存在的問題:

    • 是只有 TX 結束的時候,給自 ZPL 上層的業務返回結果。

    • 但是此時 TXG 的 sync 是異步執行,有可能 TXG 的 uber 還沒更新。

    • TXG 中的 TX 業務操作所產生的最新的空間 layout 狀態還未持久化的磁盤。

    • 如果此時的發生了 crash ,業務已經返回給上層應用。

    • 但是重啟之后的空間狀態是 TX 未操作的狀態,從而導致數據不一致的問題。

    • 為了避免這種不一致,這就需要介紹 Intend log 機制了。

未完待續

受篇幅限制,本文僅介紹了 ZFS 中的存儲池、塊管理與事務模型,后續我們會繼續分享 Intend log、ZIO、DMU、ARC、Snap、Clone等機制。

Summary

來自Linus的一盆冷水:《Don’t Use ZFS on Linux: Linus Torvalds》(https://itsfoss.com/linus-torvalds-zfs/)。我們並不需把 Linus 的每句話奉為圭臬,辯證的看待他的觀點,並且 Linus 主要表達的還是對 Oracle 的不信任,以及可維護性方面擔憂,並沒有對 ZFS 優秀的設計噴垃圾話。直到今天再看二十年前設計的 ZFS,它的 COW,TXG,ZIO,LOG,塊管理的設計理念依然讓我驚嘆不已,ZFS 針對數據存儲的痛點,從最底層設計進行革命性的優化和創新。

Quote

  1. ZFS 分層架構設計:https://farseerfc.me/zhs/zfs-layered-architecture-design.html

  2. ZFS 那點事:https://tim-tang.github.io/blog/2016/05/31/smartos-zfs-you-should-know#disqus_thread

  3. 初學者指南:ZFS 是什么,為什么要使用 ZFS:https://zhuanlan.zhihu.com/p/45137745

  4. ZFS 文件系統簡介及特性:http://www.freeoa.net/osuport/storagebak/zfs-intro-feature_2768.html

  5. WIKI ZFS:https://en.wikipedia.org/wiki/ZFS#History

  6. ZFS the internals:https://www.bsdcan.org/2008/schedule/attachments/58_BSDCan2008-ZFSInternals.pdf

  7. ZFS On-Disk Specification:http://www.giis.co.in/Zfs_ondiskformat.pdf

  8. ZFS On-Disk Data Walk:https://www.yumpu.com/en/document/read/3872732/zfs-on-disk-data-walk-ftp-directory-listing-bruning-systems

  9. Space Maps:https://blogs.oracle.com/bonwick/space-maps

  10. Don’t Use ZFS on Linux: Linus Torvalds:https://itsfoss.com/linus-torvalds-zfs/

  11. ZFS Features & Concepts TOI:https://wiki.lustre.org/images/4/49/Beijing-2010.2-ZFS_overview_3.1_Dilger.pdf

  12. ZFS 磁盤空間管理(分配、釋放原理):https://blog.51cto.com/zhangyu/1862811

作者

肖文文 QingStor 文件存儲開發工程師

本文由博客一文多發平台 OpenWrite 發布!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM