ZFS存储池是由一个虚拟设备集合构成的。这里面一共有两种虚拟设备:物理虚拟设备(physical virtual devices,也称为叶虚拟设备,leaf vdevs),以及逻辑虚拟设备(logical virtual devices,也称为内部虚拟设备,interior vdevs)。物理设备是一个可写的块设备(好比一个磁盘);逻辑设备在概念上的一组物理设备。数组
Vdev是经过一个以物理设备为叶子节点的树来管理的。每个pool都有一个被称为“root vdev”的设备做为这棵树的根。root节点的全部直接子节点(不管是逻辑的仍是物理的)都被称为top-level vdevs。下图显示了一个由两个Mirror构成的pool的虚拟设备树。第一个Mirror(标签为M1)有两个磁盘,分别经过“vdev A”和“vdev B”来表示;一样的,第二个Mirror(标签为M2)也有两个磁盘,分别经过“vdev C”和“vdev D”来表示。虚拟设备A,B,C,D都是物理虚拟设备。“M1”和“M2”都是逻辑虚拟设备,因为M1和M2都是root的直接子节点,因此都是“top-level vdevs”。
布局
Pool中的每个物理虚拟设备都包含一个256K的结构体——vdev_label。这个Label描述了这个设备以及同一top-level vdev下的其余全部虚拟设备的信息。例如:对于虚拟设备C,它的vdev label中包含虚拟设备C,D以及M2的信息。vdev label的详细信息在下一节中详细描述。学习
使用vdev label有两个目的:它提供访问pool中信息的入口;另外它还被用于验证pool的完整新和可用性。为了保证vdev label老是合法可用的,咱们使用冗余和特殊的更新方式。冗余:pool中的每一个物理虚拟设备都有4个相同的Label,同一个设备上的4份是彻底相同的,不一样设备则是不一样的。更新:Label的更新过程当中,使用两阶段的事务来更新,这样确保虚拟设备上至少有一个合法的Label。下面详细介绍一下Label的冗余和更新技术。ui
pool中的每一个物理虚拟设备都有4份相同的Label,在更新过程当中,这4份Label每个均可以用来访问、验证pool中的数据。当一个设备被添加到pool中时,ZFS在设备的最前面放两个Label,最后面也放两个Label。下图显示四个Label的分布,N表示设备的总大小,L0、L1为前两个Label,L2、L3为后两个Label。spa
<span "="">考虑到通常状况下设备的损坏都是一个连续的段,因此要将Label放在两个不一样的地方。翻译
Label<span "="">的位置是在设备加入pool时就设定好了,所以Label的更新并不能像ZFS对待其余数据那样采用Copy-On-Write技术,也就是说,对Label的更新是直接覆盖原有的数据。可是在写任何数据的过程当中都有可能发生错误,为了保证ZFS任什么时候候老是有一个合法的Label,Label的更新过程被分为两个阶段。第一阶段更新偶数的Label(L0、L2),若是在这一阶段中任什么时候刻发生了错误,奇数Label(L1、L3)仍然是合法的。在L0、L2更新结束以后,第二阶段则是更新L1、L3。code
vdev label的信息被分红了4部分:8K的空闲空间(Blank Space),8K的boot header信息,112K的name-value键值对以及128K的1K大小的uberblock结构数组。下图显示了L0的扩展视图。对象
ZFS支持使用VTOC(Volume Table of Content)和EFI两种方式来描述磁盘布局。EFI标签并非Slice的一部分(它有本身的保留空间),VTOC标签必须被写在Slice0的前8K空间内,因此为了支持VTOC标签,vdev_label的前8K空间被保留下来防止重写了VTOC标签。递归
保留,之后使用。事务
后面的112KB存储描述这个设备以及其关联设备的键值对。关联设备即:同一个top level vdev下的设备。好比下图中的灰色圆内部的A,B,M1三个设备。
<span "="">全部的键值对都使用XDR encoded nvlists存储。关于更多的XDR encoding或是nvlists请参见 libnvpair(3LIB)和nvlist_free(3NVPAIR)man手册。如下的键值对都被包含在这112K的部份内。
属性 |
名称 |
值 |
描述 |
Version |
version |
DATA_TYPE_UINT64 |
版本号 |
Name |
name |
DATA_TYPE_STRING |
当前vdev所属的pool名称 |
State |
state |
DATA_TYPE_UINT64 |
当前pool的状态,pool全部状态有: POOL_STATE_ACTIVE 0 POOL_STATE_EXPORTED 1 POOL_STATE_DESTROYED 2 |
Transaction |
txg |
DATA_TYPE_UINT64 |
将这个Label写入磁盘的事务组号 |
Pool Guid |
pool_guid |
DATA_TYPE_UINT64 |
pool的全局惟一标识符 |
Top Guid |
top_guid |
DATA_TYPE_UINT64 |
top_vdev的全局惟一标识符 |
Guid |
guid |
DATA_TYPE_UINT64 |
当前vdev的全局惟一标识符 |
Vdev Tree |
vdev_tree |
DATA_TYPE_NVLIST |
vdev_tree使用递归的方式来描述。下文详细描述 |
vdev_tree递归地描述与当前vdev相关的vdev的信息。
每一个vdev_tree都包含如下信息:
名称 |
值 |
描述 |
type |
DATA_TYPE_STRING |
代表vdev的类, 有如下几种类型: disk 叶设备:块设备存储 file 叶设备:文件存储 mirror 内部设备:mirror raidz 内部设备:raidz replacing 内部设备:ZFS在设备替换时使用 root 内部设备:vdev tree的根设备 |
id |
DATA_TYPE_UINT64 |
当前vdev在父节点孩子中的序号 |
guid |
DATA_TYPE_UINT64 |
当前树的全局惟一标识符 |
path |
DATA_TYPE_STRING |
设备路径(仅用于叶虚拟设备) |
devid |
DATA_TYPE_STRING |
vdev_tree的设备ID,仅用于disk类型的虚拟设备 |
metaslab_ arrary |
DATA_TYPE_UINT64 |
由对象号构成的数组。数组中的每一个元素(ma[i])metaslab对应的空间图的对象号 |
metaslab_ shift |
DATA_TYPE_UINT64 |
log2(metaslab大小) |
ashift |
DATA_TYPE_UINT64 |
log2(当前top level vdev的最小可分配单元,block大小) |
children |
DATA_TYPE_NVLIST_ARRARY |
子节点的vdev_tree |
nvlist以后就是uberblock的数组。uberblock是访问pool中数据的入口。任意时刻,只有一个uberblock处于激活状态,全部uberblock中事务组编号最高且经过SHA-256 checksum验证合法的uberblock为激活的uberblock,它相似于UFS文件系统中的超级块。
为了可以持续访问激活的uberblock,激活的uberblock永远不会被覆盖。全部对uberblock的修改都是经过写入uberblock数组中的另一个元素来完成的。在写入新的uberblock时,事务组编号以及时间戳都是在同一个原子性的操做中完成。Uberblock经过循环的方式写入。
下图显示两个uberblock。
uberblock技术细节
uberblock按照机器的字节模式存储。
ub_magic
用于表示该设备包含ZFS数据。ub_magic的值为0x00bab10c(oo-ba-bloc)
ub_version
版本号,与前文中键值对的版本号意义相同。
ub_txg
ZFS中全部的写操做都是经过事务组来完成的。每一个事务组都有一个对应的事务组编号。ub_txg用来表示写入这个uberblock的事务组编号。ub_txg必须大于等于前面键值对中的txg编号。
ub_guid_sum
用来检验pool中全部设备的可用性。当pool被打开后,ZFS遍历pool中全部的叶虚拟设备计算这些GUID的和,而后与ub_guid_sum进行比较,来验证pool中磁盘的可用性。
ub_timestamp
当前uberblock写入的时间戳(1970年1月1日至今的秒数)
ub_rootbp
这是一个blkptr结构体,包含了MOS的位置。MOS和blkptr将在后文中详细描述。
L0和L1以后有一个3.5M的保留空间。