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);
}
總結
以上是生活随笔為你收集整理的zblock 结构_zfs raidz结构详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么叫做罗列式_陈列,罗列是什么意思?
- 下一篇: python psd_Python ps