治肾虚不含糖,专注内核性能优化二十年。 https://github.com/KnightKu
分类:
2018-08-28 15:28:40
原文地址:ZFS磁盘格式(1) 作者:qincp-
ZFS存储池(pool)由一组虚设备构成。有两种类型的虚设备:物理虚设备(有时称为叶vdev)和逻辑虚设备(有时称为内部vdev)。物理vdev是一个可写媒体块设备(例如,一个磁盘)。逻辑vdev是物理vdev的一个逻辑组合。
Vdev按树形组织,物理vdev是这个树的叶。所有的存储池都有一个特殊的逻辑vdev——根vdev,是这个树的根。所有根vdev的第一代孩子(物理或者逻辑)叫做顶层vdev。下图显示了一个包含两个镜像的pool配置的vdev树。第一个镜像(M1)包含两个磁盘,分别为vdev A和vdev B;第二个镜像M2包含两个磁盘vdev C和vdev D。vdev A,B,C,D都是物理vdev。M1和M2为逻辑vdev,他们也是顶层vdev。
图1 vdev树示例
Pool中的每个物理vdev包含一个256KB的结构,这就是vdev标签。标签中存放了描述这个物理vdev以及所有以同一个顶层vdev为祖先的其他vdev的信息。例如,上图的vdev C的标签结构包含C,D,M2的信息。下一节将详细描述Vdev的标签信息。
Vdev标签有两个作用:提供对pool内容的访问,以及用于pool完整性和可用性效验。为保证标签始终可用和有效,使用了冗余和阶段更新机制。每个Vdev中,有四个完全相同的标签的拷贝。在标签更新过程中,使用了两阶段事务,保证磁盘上至少有一个标签是有效的。
Pool中每个物理Vdev都有标签的四个拷贝。除标签更新过程的很小时间窗外,这四个标签内容是完全相同,可以使用其中任何一个。当设备添加到pool中时,ZFS将两个标签放在设备的头,两个标签放在设备的尾部。下图显示了设备上这些标签的分布。L0和L1代表前两个,L2和L3代表后两个标签。
图2 块设备上的vdev标签
典型的设备破坏一般发生在连续的物理位置,基于这个假设,将标签分为放在头部和尾部提高了在异常发生时的数据可用度。
Vdev标签的位置在设备添加到pool时已经固定,这使得标签和ZFS其他对象不同,没有COW机制。所以,在vdev标签更新时,标签的内容被直接覆盖,这样覆盖可能发生错误。为保证ZFS始终有可用的标签,在更新时,使用了阶段提交。更新的第一个阶段,向磁盘写L0和L2,此时L1和L3依然有效。一旦L0和L2成功写入,第二阶段再将L1和L3写入。这个机制保证了所有时候,磁盘上都有有效的标签。
Vdev标签的内容分为4部分,8KB空白空间,8KB的boot头信息,112KB的name-value对,以及128个大小为1KB的uberblock结构。下图是L0的进一步扩展图示:
图3 vdev标签的组成
ZFS支持VTOC(卷内容表,Volume Table of Content)和EFI磁盘标签两种方式进行磁盘格式描述。EFI标签有自己的保留空间,未作为切片部分写入,VTOC标签必须写入切片0的第一个8K。所以,为支持VTOC,vdev标签的第一个8K保留,防止VTOC磁盘标签的覆盖。
Boot块头是一个8KB结构,保留用于以后使用。块的内容以后作为本文未来的附录进行说明。
标签的112KB空间存放一组name-value对,用于描述这个vdev和所有关联的vdev。和本vdev以同一个顶层vdev为祖先的所有其他vdev都是相关联的vdev。例如,下图中设备A的vdev标签包含了高亮显示的子树的信息:包括vdev A,B以及M1。
图4 高亮圈中显示了vdev树的相关联vdev
所有name-value对按XDR编码nvlist方式存储。更多关于XDR编码或者nvlist的信息,参见libnvpair(3LIB)和nvlist_free(3NVPAIR)手册。Vdev便签的这112KB中包含了如下name-value对:
Version:
名称: “version”
值: DATA_TYPE_UINT64
描述: 磁盘格式版本号,当前为 “
Name:
名称: “name”
值: DATA_TYPE_STRING
描述: vdev所属的pool的名称
State:
名称: “state”
值: DATA_TYPE_UINT64
描述: Pool的状态,如下表
表1 POOL状态和值
状态 |
值 |
POOL_STATE_ACTIVE |
0 |
POOL_STATE_EXPORTED |
1 |
POOL_STATE_DESTROYED |
2 |
Transaction
名称: “txg”
值: DATA_TYPE_UINT64
描述: 标签写入磁盘的事务组编号
Pool Guid
名称: “pool_guid”
值: DATA_TYPE_UINT64
描述: pool的全部唯一标识(guid)
Top Guid
名称: “top_guid”
值: DATA_TYPE_UINT64
描述: 该子树对应的顶层vdev的全部唯一标识
Guid
名称: “guid”
值: DATA_TYPE_UINT64
描述: 该vdev的全部唯一标识
Vdev Tree
名称: “vdev_tree”
值: DATA_TYPE_NVLIST
描述: 用于描述如图1和图4中vdev树的等级特性的Nvlist结构。Vdev_tree递归的描述每一个子树中所有相关的vdev。下图展示图1和图4中vdev A中的vdev_tree结构
图5 图1的vdev A的vdev_tree
每个vdev_tree nvlist包含下述的元素,注意并非所有nvlist元素都适合所有的vdev类型,vdev_tree中可能值包含下述元素的一个子集。
名称: “type”
值: DATA_TYPE_STRING
描述: 表示vdev类型的字符串,如下表
类型 |
描述 |
“disk” |
叶vdev:块存储 |
“file” |
叶vdev:文件存储 |
“mirror” |
内部vdev:镜像 |
“raidz” |
内部vdev:raidz |
“replacing” |
内部vdev:镜像vdev的一个变种,磁盘替换时供ZFS使用 |
“root” |
内部vdev:vdev树根 |
名称: “id”
值: DATA_TYPE_UINT64
描述: 这个vdev在其父节点的孩子数组中的下标
名称: “guid”
值: DATA_TYPE_UINT64
描述: vdev_tree元素的全局唯一标识
名称: “path”
值: DATA_TYPE_STRING
描述: 设备路径,仅用于叶vdev
名称: “devid”
值: DATA_TYPE_STRING
描述: vdev_tree元素的设备ID,仅用于磁盘类型的vdev
名称: “metaslab_array”
值: DATA_TYPE_UINT64
描述: 这是一个对象编号,该对象包含了一个对象编号数组,数组的每一个元素是对应mataslab空间映射的对象编号
名称: “metaslab_shift”
值: DATA_TYPE_UINT64
描述: metaslab尺寸,以2为底的对数表示
名称: “ashift”
值: DATA_TYPE_UINT64
描述: 这个顶层vdev的最小可分配单元的大小,以2为底的对数表示。对于Raidz,当前取值10,其他类型取值为9。
名称: “asize”
值: DATA_TYPE_UINT64
描述: 这个顶层vdev中的可分配的空间大小。
名称: “children”
值: DATA_TYPE_NVLIST_ARRAY
描述: 包含该vdev_tree元素的所有孩子的Vdev_tree nvlist数组
Vdev标签中,name-value对列表之后就是一个uberblock数组。Uberblock包含访问pool内容需要的信息。某个时刻pool中只有一个uberblock是激活的。拥有最高事务组编号并且SHA-256效验和有效的uberblock是激活的uberblock。
为保证对激活uberblock的持续可访问,激活uberblock绝不进行覆盖。所有对uberblock的更新将向数组中除当前激活uberblock的另外一个元素写入。写入新的uberblock时,事务组编号和时间戳都进行更新,写入结束后,新的uberblock以一个原子动作的方式称为新的激活uberblock。在pool中的uberblock数组中,以一种循环方式进行写操作。下图对数组中的两个元素进行了扩展呈现:
图6 Uberblock数组
Uberblock技术细节
Uberblock以机器字节序方式存储,包含如下内容:
Ub_magic
用于标识包含ZFS数据的设备的64位魔数。Ub_magic的值为0x00bab
表3 磁盘上的Uberblock魔数
机器字节序 |
Uberblock魔数值 |
大尾 |
0x00bab |
小尾 |
0x0cb1ba00 |
Ub_version
用于标识该数据的格式的版本。当前版本号为0x1。这个域和
Ub_txg
ZFS的所有写都是以事务组方式进行的。每一个组和一个事务组编号关联。Ub_txg值就是该uberblock写入时的事务组编号,它必须大于或等于已存在的事务组编号。
Ub_guid_sum
标识pool中的vdev的可用性。当pool打开时,ZFS遍历pool中的所有叶vdev,计算经过的所有GUID的和(如
Ub_timestamp
该uberblock写入的时间,从
Ub_rootbp
一个Blkptr结构,包含MOS位置信息。MOS在第四章说明,blkptr在第二章说明。
L0和L1之后是3.5MB大小的空间预留做以后使用。
图7 vdev标签格式以及boot块预留空间