zblock 结构_zfs raidz结构详解

直接进入主题,几个重点:

1、RAIDZ是和ZFS密切配合的一种RAID模型,RAIDZ在接收数据时是由ZFS指定一个可变长的数据流。根据这个数据流的大小不同,RAIDZ在存储时也会有不同。

2、RAIDZ相对于传统RAID,没有严格的blocksize概念,如果数据流小,甚至可以是1扇区的blocksize。

同时相对于传统RAID,也没有一个标准的校验模式,虽然比较像RAID5,但假如是1扇区的IO,就更像RAID1了。

3、RAIDZ也可以支持多重冗余,内部称之为RAIDZ_P(即通常提到的RAIDZ,支持1块硬盘掉线)、RAIDZ_Q(支持2块盘同时掉线,如同RAID6)、 RAIDZ_R(支持3块盘同时掉线)

4、RAIDZ的IO地址是带有校验的地址值,不同于传统RAID校验(传统RAID的校验区域对于文件系统而言是不可见的)

5、RAIDZ_P的校验位置在每次IO的位置相对一致,但为了负载均衡,约定,如果IO首地址是偶数1M内(即offset / 1M为偶数),校验在数据的最前面;如果IO首地址是奇数1M内,校验插入数据流,在第一个扇区(从0开始计数)。此规则仅适用于RAIDZ_P,不适用于RAIDZ_Q,RAIDZ_Q

6、RAIDZ约定,一次IO一定是校验数+1的整数倍,比如RAIDZ_P一次IO下来如果是3扇区,最后会有一个SKIP扇区(因此,才会有5中校验要交换的做法),zfs为了保证空间再分配时不至于出现孔洞,所以在申请空间时,就必须满足是(nparity + 1)的整数倍,就样的好处在于,任意申请的空间,重用时,至少都是够最小运算模式的。

比如:RAIDZ一段连续的空间中间,释放了6个扇区,如果再重用时只用了5个,那剩下的1个还是会浪费掉,无法分配。如果是RAIDZ2或RAIDZ3,这种问题就更突出了。反正无法避免浪费,为了运算简洁,干脆在每次申请时就按整块的处理,确保无论如何释放,都不会在下一次IO时出现浪费。

7、为了保证IO高效,zfs一次写入IO时,会优先以vdev为单位连续写入,所以,会很不像1扇区为条带大小的RAID5,具体见结构描述示例:假设有5块硬盘组成RAIDZ,分别是DISK1,DISK2,DISK3,DISK4,DISK5顺序也按此排列:

情况一:如果一次IO大小为1扇区,RAIDZ VDEV的offset地址为X,则(x/5)先计算出在哪个条带,再通过(x % 5)得到开始盘序,在同一条带上再向后挪一个磁盘(可能会返回disk1),这2个扇区一个是数据,一个是校验(此情况RAIDZ无需填充),就完成了此次IO的存储

示例:如果x为10,位于偶数1M内,设数据为D,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#001234

sec#156789

sec#21011P=DD

示例:如果x位于奇数1M内,设数据为D,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#51201234

sec#51356789

sec#5141011DP=D

情况二:如果一次IO大小为2扇区,RAIDZ VDEV的offset地址为X,设"+"表示异或

示例:如果x为10,位于偶数1M内,设数据为D1,D2,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#001234

sec#156789

sec#21011P=D1+D2D1D2

sec#3SKIP

示例:如果x位于奇数1M内,设数据为D1,D2,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#51201234

sec#51356789

sec#5141011D1P=D1+D2D2

sec#515SKIP

情况三:如果一次IO大小为5扇区,RAIDZ VDEV的offset地址为X,设"+"表示异或

示例:如果x为10,位于偶数1M内,设数据为D1,D2,D3,D4,D5,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#001234

sec#156789

sec#21011P=D1+D3+D4+D5D1D3

sec#3D4D5P=D2D2SKIP

sec#4

示例:如果x位于奇数1M内,设数据为D1,D2,D3,D4,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#51201234

sec#51356789

sec#5141011D1P=D1+D3+D4+D5D3

sec#515D4D5D2P=D2SKIP

sec#516

情况四:如果一次IO大小为6扇区,RAIDZ VDEV的offset地址为X,设"+"表示异或

示例:如果x为10,位于偶数1M内,设数据为D1,D2,D3,D4,D5,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#001234

sec#156789

sec#21011P=D1+D3+D5+D6D1D3

sec#3D5D6P=D2+D4D2D4

sec#4

示例:如果x位于奇数1M内,设数据为D1,D2,D3,D4,D5,校验为P,则数据会存储在:

disk1disk2disk3disk4disk5

sec#51201234

sec#51356789

sec#5141011D1P=D1+D3+D5+D6D3

sec#515D5D6D2P=D2+D4D4

sec#516

源码主要位于module\zfs\vdev_raidz.c,涉及分配规则的函数为vdev_raidz_map_alloc(),仔细对源码解读、注释后的结果如下:/*

* Divides the IO evenly across all child vdevs; usually, dcols is

* the number of children in the target vdev.

*

* Avoid inlining the function to keep vdev_raidz_io_start(), which

* is this functions only caller, as small as possible on the stack.

*/

/*

*分配原则是需要在所有子vdev之间平均分配IO,dcols是目标vdev中的子节点数。

*避免内联函数以保持vdev_raidz_io_start(),它是这个函数只有调用者,在堆栈上要尽可能小。

by:张宇

*/

noinline static raidz_map_t *

vdev_raidz_map_alloc(zio_t *zio, uint64_t unit_shift, uint64_t dcols,

uint64_t nparity)

{

raidz_map_t *rm;

/* The starting RAIDZ (parent) vdev sector of the block. */

/* 在父vdev上的扇区编号,其实就是RAIDZx这个vdev,DVA中标注的扇区号*/

uint64_t b = zio->io_offset >> unit_shift;

/* The zio's size in units of the vdev's minimum sector size. */

/*一次IO的字节大小,其实就是RAIDZx这个vdev,一次IO的有效数据大小(不包含校验,扇区数*每扇区字节数)*/

uint64_t s = zio->io_size >> unit_shift;

/* The first column for this stripe. */

/*条带的第一列,是用父vdev的扇区编号对vdev数(raid成员数)取余的结果*/

uint64_t f = b % dcols;

/* The starting byte offset on each child vdev. */

/*计算每个子vdev的起始字节位置,用父vdev的扇区号简单地除以"子vdev数量"*/

uint64_t o = (b / dcols) <

uint64_t q, r, c, bc, col, acols, scols, coff, devidx, asize, tot;

/*

* "Quotient": The number of data sectors for this stripe on all but

* the "big column" child vdevs that also contain "remainder" data.

*/

/*q表示共占用多少完整行(以每个扇区为行高)*/

q = s / (dcols - nparity);

/*

* "Remainder": The number of partial stripe data sectors in this I/O.

* This will add a sector to some, but not all, child vdevs.

*/

/*r表示除去整数行外,不足一行部分,还剩多少io扇区(仅计数据,不计校验)*/

r = s - q * (dcols - nparity);

/* The number of "big columns" - those which contain remainder data. */

/*尾部扇区数,加上可能的校验的大小---如果尾部扇区数为0,表示正好凑整N行,就不用另加校验扇区了。*/

bc = (r == 0 ? 0 : r + nparity);

/*

* The total number of data and parity sectors associated with

* this I/O.

*/

/*表示算上校验的完整扇区总数*/

tot = s + nparity * (q + (r == 0 ? 0 : 1));

/* acols: The columns that will be accessed. */

/* scols: The columns that will be accessed or skipped. */

/*  acols:需要存取的io列数 */

/*  scols:加上可能的skip后的io列数 */

/*如果io扇区数量不必要动用所有vdev,则没必要所有列都处理*/

if (q == 0) {

/* Our I/O request doesn't span all child vdevs. */

acols = bc;

scols = MIN(dcols, roundup(bc, nparity + 1));

} else {

acols = dcols;

scols = dcols;

}

ASSERT3U(acols, <=, scols);

rm = kmem_alloc(offsetof(raidz_map_t, rm_col[scols]), KM_SLEEP);

rm->rm_cols = acols;

rm->rm_scols = scols;

rm->rm_bigcols = bc;

rm->rm_skipstart = bc;//表示skip扇区默认位置,放在最后,这是RAIDZ列的位置顺序号,表示rm->rm_col[XXX].中的XXX

rm->rm_missingdata = 0;

rm->rm_missingparity = 0;

rm->rm_firstdatacol = nparity;//默认第一个数据块区在校验后(但后面为了均衡,会可能置换)

rm->rm_datacopy = NULL;

rm->rm_reports = 0;

rm->rm_freed = 0;

rm->rm_ecksuminjected = 0;

asize = 0;

for (c = 0; c 

col = f + c;//f是io的第一列,再求从第一列开始,依次向后

coff = o; //io起始offset

if (col >= dcols) { //如果到了列尾,折到下一行

col -= dcols;

coff += 1ULL <

}

rm->rm_col[c].rc_devidx = col;

rm->rm_col[c].rc_offset = coff;

rm->rm_col[c].rc_data = NULL;

rm->rm_col[c].rc_gdata = NULL;

rm->rm_col[c].rc_error = 0;

rm->rm_col[c].rc_tried = 0;

rm->rm_col[c].rc_skipped = 0;

if (c >= acols) //如果不足一行,且skip部分的扇区

rm->rm_col[c].rc_size = 0;

else if (c 

rm->rm_col[c].rc_size = (q + 1) <

else

rm->rm_col[c].rc_size = q <

asize += rm->rm_col[c].rc_size;//asize等于除去skip的IO字节数(包括校验)

}

ASSERT3U(asize, ==, tot <

rm->rm_asize = roundup(asize, (nparity + 1) <

rm->rm_nskip = roundup(tot, nparity + 1) - tot;//skip扇区数

ASSERT3U(rm->rm_asize - asize, ==, rm->rm_nskip <

ASSERT3U(rm->rm_nskip, <=, nparity);

for (c = 0; c rm_firstdatacol; c++)//为校验分配内存

rm->rm_col[c].rc_data = zio_buf_alloc(rm->rm_col[c].rc_size);

rm->rm_col[c].rc_data = zio->io_data; //io的原始数据,指向rm_firstdatacol(等于校验数,即相当于先跳过几列校验,之后开始按列写入真实数据)

for (c = c + 1; c 

rm->rm_col[c].rc_data = (char *)rm->rm_col[c - 1].rc_data +

rm->rm_col[c - 1].rc_size;

/*

* If all data stored spans all columns, there's a danger that parity

* will always be on the same device and, since parity isn't read

* during normal operation, that that device's I/O bandwidth won't be

* used effectively. We therefore switch the parity every 1MB.

*

* ... at least that was, ostensibly, the theory. As a practical

* matter unless we juggle the parity between all devices evenly, we

* won't see any benefit. Further, occasional writes that aren't a

* multiple of the LCM of the number of children and the minimum

* stripe width are sufficient to avoid pessimal behavior.

* Unfortunately, this decision created an implicit on-disk format

* requirement that we need to support for all eternity, but only

* for single-parity RAID-Z.

*

* If we intend to skip a sector in the zeroth column for padding

* we must make sure to note this swap. We will never intend to

* skip the first column since at least one data and one parity

* column must appear in each row.

*/

/*

如果所有数据存储用到了每一列,则存在校验块始终在同一设备上的问题。而校验块不

参与正常的IO读取,所以,从负载角度看,该设备的I/O带宽无法被有效使用。因此,

我们每隔1MB切换奇偶校验(方法是仅针对RAID-Z,每隔1M,交换校验列与第一个数据列)。

疑问1:

校验列和第一个数据列交换,会不会因为厚度不同(IO行数),导致IO片断不连续

答:

不会,因为校验列是最厚列(必须保证每一行都有校验),第一个数据列,也是最厚列

疑问2:

为什么要有padding sector?

答:

zfs为了保证空间再分配时不至于出现孔洞,所以在申请空间时,就必须满足是(nparity + 1)

的整数倍,就样的好处在于,任意申请的空间,重用时,至少都是够最小运算模式的。

比如:RAIDZ一段连续的空间中间,释放了6个扇区,如果再重用时只用了5个,那剩下的1个还是会浪费掉,

无法分配。如果是RAIDZ2或RAIDZ3,这种问题就更突出了。反正无法避免浪费,为了运算简洁,干脆在每

次申请时就按整块的处理,确保无论如何释放,都不会在下一次IO时出现浪费。

疑问3:

为什么raidz2和raidz3无需每隔1M交换校验位置

答:

raidz2和raidz3都有超过1个的校验块,反正会横跨奇偶位置,交换的意义不大(虽然PQR的负载不完全对等)

*/

ASSERT(rm->rm_cols >= 2);

ASSERT(rm->rm_col[0].rc_size == rm->rm_col[1].rc_size);

/*if(raidZ && io位置是奇数个1M){

交换第一列(校验列),与第二列(第一个数据起始列)

}

*/

if (rm->rm_firstdatacol == 1 && (zio->io_offset & (1ULL <

devidx = rm->rm_col[0].rc_devidx;

o = rm->rm_col[0].rc_offset;

rm->rm_col[0].rc_devidx = rm->rm_col[1].rc_devidx;

rm->rm_col[0].rc_offset = rm->rm_col[1].rc_offset;

rm->rm_col[1].rc_devidx = devidx;

rm->rm_col[1].rc_offset = o;

//rm->rm_skipstart = bc;

//bc=尾部扇区数,加上校验块的大小

//如果padding扇区正好位于第0列,被上面交换过后,就有错误了

if (rm->rm_skipstart == 0)

rm->rm_skipstart = 1;

}

zio->io_vsd = rm;

zio->io_vsd_ops = &vdev_raidz_vsd_ops;

return (rm);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值