ZFS History
-
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。
-
-
ZFS 的開源:在 2005 年,Sun 開源 Solaris 的大部分,包括 ZFS 開源;開源項目 OpenSolaris。
-
illumos 項目成立,OpenZFS:
- 在 2010 年,Sun 被 Oracle 收購,ZFS 成為 Oracle 的注冊商標。Oracle 停止為 OpenSolaris 和 ZFS 項目提供更新的源代碼,三分之二的 ZFS 核心開發者決定離開 Oracle 公司。
- illumos 項目的成立,維護已經存在的開源的 Solaris 代碼,並且在 2013 年成立 OpenZFS 以配合 ZFS 開源的發展。
-
ZFS on Linux:2013 年,Linux 上 ZFS 的第一個穩定版本,繼續發展。
ZFS Overview
ZFS(Zettabyte File System)是一個跨時代的文件系統,被稱為最后一個單機文件系統,已經被證實可以移植到多種 OS 平台上。它擁有如下特性:
-
完全兼容 posix 語義(ZPL: ZFS POSIX Layer)。
-
提供邏輯卷功能(ZVOL: ZFS Volumes)。
-
提交完善的管理功能:通過 libzfs 的工具,經過 ioctl 發送管理命令到 zfs kernel module。
-
提供接近無限的存儲空間:將物理存儲設備進行池化管理,文件系統建立在設備存儲池上。當文件系統空間不夠,可以將物理設備動態添加存儲池中。存儲池上的單個文件系統極限值:
-
支持 2^{48} 個快照。
-
支持 2^{48} 個文件。
-
單個文件最大 16EB。
事務模型 + COW:
-
-
事務模型和 COW 是強綁定的關系。
-
COW(Copy On Write):對文件的數據不是原地直接修改,而是拷貝更新的方式,數據塊的內容更新不存在中間狀態。
-
事務:保證了對文件的一組操作都是原子性的,並且每個事務之間是相互獨立的。所有文件的同一個事務 ID 的集合打包成一個事務組,進行從 file 到 root 的全局的拓撲的更新。
-
事務 TX 和事務組 TXG 的關系(在事務模型章節詳細介紹):
-
XG:在同一個事務 ID(TXG-num)下的 TX 構成一個事務組 TXG
-
當一個 TXG 中的被需要改的數據達到一個閾值(臟數據閾值),或者周期到了,發生一次事務組的輪轉,在這個事務組的 sync 結束之后,生成一個全新的 root 的拓撲樹(layout)。
-
- 端到端的數據安全性:
-
256 位的 Checksum,Checksum 的數據存儲在父親節點上。
-
形成一棵自驗證的 Merkle Tree, 當 ZFS 在 load 數據時會進行校驗。
-
它在負責讀取數據的時候會自動和 256 位校驗碼進行校驗,會主動發現這種 Silent Data Corruption:
-
通過相應的 Mirror 硬盤或者通過 RAID-Z 陣列中其他硬盤得到正確的數據返回給上層應用,並且同時自動修復原硬盤的 Corruption Data。
-
數據恢復也可以給通過讀取有效副本的方式進行修復:比如像 ZFS 的 spacemap 數據會存儲多份。
-
- 支持快照和克隆
-
由於 ZFS 采用的 COW 機制,每次的更新 new 數據均不會破壞磁盤上已有的 old 數據。
-
ZFS 可以根據需要保留old 數據。
-
這是實現快照功能的基礎,實際的快照功能實現是選擇一個 TXG-num 作為快照點,將 old root 下拓撲的保存起來,不去釋放被更新的數據塊地址。
-
當然實際的快照功能要復雜的多,這里不做贅述。
-
支持數據去重功能
-
支持數據壓縮功能
存儲池
存儲池 + 塊管理
大多數單機文件系統是物理設備強綁定的,一旦在某個物理設備上格式化文件系統之后,該文件系統的可用空間和物理設備大小息息相關。但是 ZFS 將實際的物理設備和文件系統進行隔離,文件系統是建立在存儲池上,存儲池上的文件系統共用存儲池的物理空間,同時可以動態添加物理設備到存儲池中,以增加存儲池的可用空間。
- 存儲池中的設備管理是一種樹形結構進行管理的,如下圖所示:
使用如下命令創建一個存儲池 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
- Label 磁盤結構:
-
在 ZFS 中直接的分配空間的是虛擬設備 VDEV 去完成的,每個 VDEV 下的 physical devices 都存在如下的 Label 信息,在物理設備的首尾兩個,共四個。
-
更新方式是兩階段更新的:在一個 TXG 中 sync 的結束階段先更新 L0,L2;如果成功再更新 L1 和 L3。
-
這樣更新和布局的好處是:無論何時掉電,總會有一個 Label 是可用的。
-
Name/Vaule 記錄的就是當前的 VDEV 下的物理設備的拓撲關系
-
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
塊管理
- 傳統方式:
談到存儲池,也免不了需要介紹塊管理機制,ZFS 的塊管理機制是不同於其他的單機文件系統的管理方式。首先需要介紹一下傳統的幾種塊管理方式:
- 位圖管理:最常用的空間管理方法是位圖。最小空間單元狀態(分配或者空閑)用一個 bit 位表示。
-
列如:4TB 磁盤,最小分配單元 4KB 需要 4KKKK}/4K/8=256MB;但是隨着空間的增大比如 324T 的空間,這個位圖的大小占用的內存空間就變得無法忽視了,並且每次空間的申請和釋放都是要持久化位圖信息到物理設備上,導致大量的磁盤 IO 操作,存在寫放大問題。
-
解決方法:分而治之,使用多級管理的方式,比如:將 4TB 的空間分為多組,以位圖組的方式進行管理,以二級甚至多級的方式管理位圖都是可以的。如果有位圖信息需要寫入磁盤,只需要更新有改變的位圖組信息到物理設備上,磁盤 IO 次數大大減少。
-
存在的問題:如果刪除文件,該文件的空間分布在所有的位圖組上,導致更新位圖組的磁盤 IO 的並沒有減少;位圖管理方式無法避免隨機釋放的帶來的大量的磁盤 IO 問題。
-
B*樹:另外一個常用的管理空閑空間的方式使用 B-tree 管理,葉子節點存儲實際的空閑的的偏移和大小。
-
優點:分配連續的空間效率很高。
-
存在的問題:刪除大文件時釋放空間同樣會帶來大量的隨機寫,需要更新大量的葉子節點信息,同樣存在寫放大的問題。
- ZFS 的管理方式
一種全新的空間管理方式:將空間的分配和釋放行為作為 log 存入到磁盤中,而不是將空間的狀態寫入到磁盤。內存中維護的是可用空間的 range-tree< AVL tree >,item 是 offset/size 。周期性的將分配和釋放的記錄寫入到磁盤中。
當系統重新啟動的時候,在內存中重做全部的 allocation 和 free 的記錄,那么可以在內存中構建可用空間的 range-tree。
-
如果有很多分配釋放被相互抵消了,會對之前累計寫入的 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。
事務模型
-
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 地址。
-
-
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
-
ZFS 分層架構設計:https://farseerfc.me/zhs/zfs-layered-architecture-design.html
-
ZFS 那點事:https://tim-tang.github.io/blog/2016/05/31/smartos-zfs-you-should-know#disqus_thread
-
初學者指南:ZFS 是什么,為什么要使用 ZFS:https://zhuanlan.zhihu.com/p/45137745
-
ZFS 文件系統簡介及特性:http://www.freeoa.net/osuport/storagebak/zfs-intro-feature_2768.html
-
ZFS the internals:https://www.bsdcan.org/2008/schedule/attachments/58_BSDCan2008-ZFSInternals.pdf
-
ZFS On-Disk Specification:http://www.giis.co.in/Zfs_ondiskformat.pdf
-
ZFS On-Disk Data Walk:https://www.yumpu.com/en/document/read/3872732/zfs-on-disk-data-walk-ftp-directory-listing-bruning-systems
-
Space Maps:https://blogs.oracle.com/bonwick/space-maps
-
Don’t Use ZFS on Linux: Linus Torvalds:https://itsfoss.com/linus-torvalds-zfs/
-
ZFS Features & Concepts TOI:https://wiki.lustre.org/images/4/49/Beijing-2010.2-ZFS_overview_3.1_Dilger.pdf
-
ZFS 磁盤空間管理(分配、釋放原理):https://blog.51cto.com/zhangyu/1862811
作者
肖文文 QingStor 文件存儲開發工程師
本文由博客一文多發平台 OpenWrite 發布!