关于云计算:存储大师班-ZFS存储池块管理与事务模型

34次阅读

共计 7425 个字符,预计需要花费 19 分钟才能阅读完成。

ZFS History

  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

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:

  5. 事务模型和 COW 是强绑定的关系。
  • COW(Copy On Write):对文件的数据不是原地间接批改,而是拷贝更新的形式,数据块的内容更新不存在中间状态。
  • 事务:保障了对文件的一组操作都是原子性的,并且每个事务之间是互相独立的。所有文件的同一个事务 ID 的汇合打包成一个事务组,进行从 file 到 root 的全局的拓扑的更新。
  • 事务 TX 和事务组 TXG 的关系(在事务模型章节具体介绍):

    • XG:在同一个事务 ID(TXG-num)下的 TX 形成一个事务组 TXG
    • 当一个 TXG 中的被须要改的数据达到一个阈值(脏数据阈值),或者周期到了,产生一次事务组的轮转,在这个事务组的 sync 完结之后,生成一个全新的 root 的拓扑树(layout)。
  1. 端到端的数据安全性:


  • 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 将理论的物理设施和文件系统进行隔离,文件系统是建设在存储池上,存储池上的文件系统共用存储池的物理空间,同时能够动静增加物理设施到存储池中,以减少存储池的可用空间。

  1. 存储池中的设施治理是一种树形构造进行治理的,如下图所示:

    应用如下命令创立一个存储池 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 下的物理设施的拓扑关系

  • 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 位示意。

  • 列如:4TB 磁盘,最小调配单元 4KB 须要 4KKKK}/4K/8=256MB;然而随着空间的增大比方 324T 的空间,这个位图的大小占用的内存空间就变得无奈漠视了,并且每次空间的申请和开释都是要长久化位图信息到物理设施上,导致大量的磁盘 IO 操作,存在写放大问题。
  • 解决办法:分而治之,应用多级治理的形式,比方:将 4TB 的空间分为多组,以位图组的形式进行治理,以二级甚至多级的形式治理位图都是能够的。如果有位图信息须要写入磁盘,只须要更新有扭转的位图组信息到物理设施上,磁盘 IO 次数大大减少。
  • 存在的问题:如果删除文件,该文件的空间散布在所有的位图组上,导致更新位图组的磁盘 IO 的并没有缩小;位图治理形式无奈防止随机开释的带来的大量的磁盘 IO 问题。
  • B* 树:另外一个罕用的治理闲暇空间的形式应用 B-tree 治理,叶子节点存储理论的闲暇的的偏移和大小。

  • 长处:调配间断的空间效率很高。
  • 存在的问题:删除大文件时开释空间同样会带来大量的随机写,须要更新大量的叶子节点信息,同样存在写放大的问题。
  1. 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-tor…)。咱们并不需把 Linus 的每句话奉为圭臬,辩证的对待他的观点,并且 Linus 次要表白的还是对 Oracle 的不信赖,以及可维护性方面担心,并没有对 ZFS 优良的设计喷垃圾话。直到明天再看二十年前设计的 ZFS,它的 COW,TXG,ZIO,LOG,块治理的设计理念仍然让我惊叹不已,ZFS 针对数据存储的痛点,从最底层设计进行革命性的优化和翻新。

Quote

  1. ZFS 分层架构设计:https://farseerfc.me/zhs/zfs-…
  2. ZFS 那点事:https://tim-tang.github.io/bl…
  3. 初学者指南:ZFS 是什么,为什么要应用 ZFS:https://zhuanlan.zhihu.com/p/…
  4. ZFS 文件系统简介及个性:http://www.freeoa.net/osuport…
  5. WIKI ZFS:https://en.wikipedia.org/wiki…
  6. ZFS the internals:https://www.bsdcan.org/2008/s…
  7. ZFS On-Disk Specification:http://www.giis.co.in/Zfs_ond…
  8. ZFS On-Disk Data Walk:https://www.yumpu.com/en/docu…
  9. Space Maps:https://blogs.oracle.com/bonw…
  10. Don’t Use ZFS on Linux: Linus Torvalds:https://itsfoss.com/linus-tor…
  11. ZFS Features & Concepts TOI:https://wiki.lustre.org/image…
  12. ZFS 磁盘空间治理(调配、开释原理):https://blog.51cto.com/zhangy…

作者

肖文文 QingStor 文件存储开发工程师

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

正文完
 0