Farseerfc的小窩 - architecture//farseerfc.me/2020-02-04T16:59:00+09:00ZFS 分層架構設計2020-02-04T16:59:00+09:002020-02-04T16:59:00+09:00farseerfctag:farseerfc.me,2020-02-04:/zfs-layered-architecture-design.html
<div class="label label-warning">
<strong>2020年2月9日更新過</strong></div>
<p>ZFS 在設計之初源自於 Sun 內部多次重寫 UFS 的嘗試,背負了重構 Solaris
諸多內核子系統的重任,從而不同於 Linux 的文件系統只負責文件系統的功能而把其餘功能(比如內存髒頁管理,
IO調度)交給內核更底層的子系統, ZFS 的整體設計更層次化並更獨立,很多部分可能和 Linux/FreeBSD
內核已有的子系統有功能重疊。</p>
<p>似乎很多關於 ZFS 的視頻演講和幻燈片有講到子系統架構,但是找了半天也沒找到網上關於這個的說明文檔。
於是寫下這篇筆記試圖從 ZFS 的早期開發歷程開始,記錄一下 ZFS 分層架構中各個子系統之間的分工。
也有幾段 OpenZFS Summit 視頻佐以記錄那段歷史。</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id4">早期架構</a></h2>
<p>早期 ZFS 在開發時大體可以分爲上下三層,分別是 ZPL, DMU 和 SPA ,這三層分別由三組人負責。</p>
<p>最初在 Sun 內部帶領 ZFS …</p></div>
<div class="label label-warning">
<strong>2020年2月9日更新過</strong></div>
<p>ZFS 在設計之初源自於 Sun 內部多次重寫 UFS 的嘗試,背負了重構 Solaris
諸多內核子系統的重任,從而不同於 Linux 的文件系統只負責文件系統的功能而把其餘功能(比如內存髒頁管理,
IO調度)交給內核更底層的子系統, ZFS 的整體設計更層次化並更獨立,很多部分可能和 Linux/FreeBSD
內核已有的子系統有功能重疊。</p>
<p>似乎很多關於 ZFS 的視頻演講和幻燈片有講到子系統架構,但是找了半天也沒找到網上關於這個的說明文檔。
於是寫下這篇筆記試圖從 ZFS 的早期開發歷程開始,記錄一下 ZFS 分層架構中各個子系統之間的分工。
也有幾段 OpenZFS Summit 視頻佐以記錄那段歷史。</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id4">早期架構</a></h2>
<p>早期 ZFS 在開發時大體可以分爲上下三層,分別是 ZPL, DMU 和 SPA ,這三層分別由三組人負責。</p>
<p>最初在 Sun 內部帶領 ZFS 開發的是 <a class="reference external" href="https://blogs.oracle.com/bonwick/">Jeff Bonwick</a>
,他首先有了對 ZFS 整體架構的構思,然後遊說 Sun 高層,親自組建起了 ZFS
開發團隊,招募了當時剛從大學畢業的 <a class="reference external" href="http://open-zfs.org/wiki/User:Mahrens">Matt Ahrens</a>
。作爲和 Sun 高層談妥的條件, Jeff 也必須負責 Solaris 整體的 Storage & Filesystem Team
,於是他又從 Solaris 的 Storage Team 抽調了 UFS 部分的負責人 Mark Shellenbaum 和
Mark Maybee 來開發 ZFS 。而如今昔日昇陽已然日落, Jeff
成立了獨立公司繼續開拓服務器存儲領域, Matt 是 OpenZFS 項目的負責人,兩位 Mark 則留在了
Sun/Oracle 成爲了 Oracle ZFS 分支的維護者。</p>
<div class="panel panel-default">
<div class="panel-heading">
The Birth of ZFS by Jeff Bonwick</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/dcV2PaMTAJ4"></iframe></div></div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
Story Time (Q&A) with Matt and Jeff</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/yNKZQBsTX08"></iframe></div></div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
ZFS First Mount by Mark Shellenbaum</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/xMH5rCL8S2k"></iframe></div></div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
ZFS past & future by Mark Maybee</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/c1ek1tFjhH8"></iframe></div></div>
</div>
<p>在開發早期,作爲分工, Jeff 負責 ZFS 設計中最底層的 SPA ,提供多個存儲設備組成的存儲池抽象;
Matt 負責 ZFS 設計中最至關重要的 DMU 引擎,在塊設備基礎上提供具有事務語義的對象存儲;
而兩位 Mark 負責 ZFS 設計中直接面向用戶的 ZPL ,在 DMU 基礎上提供完整 POSIX 文件系統語義。
ZFS 設計中這最初的分工也體現在了 ZFS 現在子系統分層的架構上,繼續影響(增強或者限制) ZFS
今後發展的方向。</p>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id5">子系統整體架構</a></h2>
<p>首先 ZFS 整體架構如下圖,其中圓圈是 ZFS 給內核層的外部接口,方框是 ZFS 內部子系統(
我給方框的子系統加上了超鏈接):</p>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: ZFS_Layer_Architecture Pages: 1 -->
<svg class="svg-responsive" height="626pt" viewbox="0.00 0.00 806.50 626.00" width="807pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 622)">
<title>ZFS_Layer_Architecture</title>
<polygon fill="#ffffff" points="-4,4 -4,-622 802.5,-622 802.5,4 -4,4" stroke="transparent"></polygon>
<g class="cluster" id="clust4">
<title>clusterTOL</title>
<g id="a_clust4"><a xlink:href="#tol" xlink:title="TOL">
<polygon fill="none" points="493.5,-355 493.5,-574 583.5,-574 583.5,-355 493.5,-355" stroke="#000000"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="538.5" y="-558.8">TOL</text>
</a>
</g>
</g>
<g class="cluster" id="clust9">
<title>clusterSPA</title>
<g id="a_clust9"><a xlink:href="#spa" xlink:title="SPA">
<polygon fill="none" points="415.5,-64 415.5,-283 649.5,-283 649.5,-64 415.5,-64" stroke="#000000"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="532.5" y="-267.8">SPA</text>
</a>
</g>
</g>
<!-- Filesystem API -->
<g class="node" id="node1">
<title>Filesystem API</title>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="62.5" y="-596.3">Filesystem API</text>
</g>
<!-- VFS -->
<g class="node" id="node5">
<title>VFS</title>
<ellipse cx="62.5" cy="-525" fill="none" rx="30.5947" ry="18" stroke="#000000"></ellipse>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="62.5" y="-521.3">VFS</text>
</g>
<!-- Filesystem API->VFS -->
<g class="edge" id="edge1">
<title>Filesystem API->VFS</title>
<path d="M62.5,-581.8446C62.5,-573.3401 62.5,-563.0076 62.5,-553.4964" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="66.0001,-553.2481 62.5,-543.2482 59.0001,-553.2482 66.0001,-553.2481" stroke="#000000"></polygon>
</g>
<!-- Block device API -->
<g class="node" id="node2">
<title>Block device API</title>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="437.5" y="-596.3">Block device API</text>
</g>
<!-- /dev/zvol/... -->
<g class="node" id="node6">
<title>/dev/zvol/...</title>
<ellipse cx="421.5" cy="-525" fill="none" rx="63.8893" ry="18" stroke="#000000"></ellipse>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="421.5" y="-521.3">/dev/zvol/...</text>
</g>
<!-- Block device API->/dev/zvol/... -->
<g class="edge" id="edge2">
<title>Block device API->/dev/zvol/...</title>
<path d="M433.6268,-581.8446C431.7795,-573.1849 429.5277,-562.6301 427.4684,-552.9768" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="430.8469,-552.0379 425.3375,-542.9882 424.001,-553.4984 430.8469,-552.0379" stroke="#000000"></polygon>
</g>
<!-- ZFS Management API (libzfs) -->
<g class="node" id="node3">
<title>ZFS Management API (libzfs)</title>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="684.5" y="-596.3">ZFS Management API (libzfs)</text>
</g>
<!-- /dev/zfs ioctl -->
<g class="node" id="node7">
<title>/dev/zfs ioctl</title>
<ellipse cx="684.5" cy="-525" fill="none" rx="69.5877" ry="18" stroke="#000000"></ellipse>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="684.5" y="-521.3">/dev/zfs ioctl</text>
</g>
<!-- ZFS Management API (libzfs)->/dev/zfs ioctl -->
<g class="edge" id="edge3">
<title>ZFS Management API (libzfs)->/dev/zfs ioctl</title>
<path d="M684.5,-581.8446C684.5,-573.3401 684.5,-563.0076 684.5,-553.4964" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="688.0001,-553.2481 684.5,-543.2482 681.0001,-553.2482 688.0001,-553.2481" stroke="#000000"></polygon>
</g>
<!-- NFS/Samba API (libshare) -->
<g class="node" id="node4">
<title>NFS/Samba API (libshare)</title>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="246.5" y="-596.3">NFS/Samba API (libshare)</text>
</g>
<!-- NFS/CIFS vop_rwlock -->
<g class="node" id="node8">
<title>NFS/CIFS vop_rwlock</title>
<ellipse cx="225.5" cy="-525" fill="none" rx="114.2798" ry="18" stroke="#000000"></ellipse>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="225.5" y="-521.3">NFS/CIFS vop_rwlock</text>
</g>
<!-- NFS/Samba API (libshare)->NFS/CIFS vop_rwlock -->
<g class="edge" id="edge4">
<title>NFS/Samba API (libshare)->NFS/CIFS vop_rwlock</title>
<path d="M241.4165,-581.8446C238.966,-573.0928 235.9735,-562.4053 233.2473,-552.669" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="236.6034,-551.6741 230.5367,-542.9882 229.8627,-553.5616 236.6034,-551.6741" stroke="#000000"></polygon>
</g>
<!-- VFS->NFS/CIFS vop_rwlock -->
<g class="edge" id="edge5">
<title>VFS->NFS/CIFS vop_rwlock</title>
<path d="M93.0625,-525C95.6653,-525 98.2682,-525 100.871,-525" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="100.9106,-528.5001 110.9105,-525 100.9105,-521.5001 100.9106,-528.5001" stroke="#000000"></polygon>
</g>
<!-- ZPL -->
<g class="node" id="node9">
<title>ZPL</title>
<g id="a_node9"><a xlink:href="#zpl" xlink:title="ZPL">
<polygon fill="none" points="367.5,-471 313.5,-471 313.5,-435 367.5,-435 367.5,-471" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="340.5" y="-449.3">ZPL</text>
</a>
</g>
</g>
<!-- VFS->ZPL -->
<g class="edge" id="edge6">
<title>VFS->ZPL</title>
<path d="M86.3025,-513.3546C91.5594,-511.0472 97.1562,-508.7886 102.5,-507 171.7912,-483.808 255.7337,-467.3568 303.1418,-459.0894" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="303.9854,-462.4959 313.2493,-457.3547 302.8013,-455.5968 303.9854,-462.4959" stroke="#000000"></polygon>
</g>
<!-- ZVOL -->
<g class="node" id="node10">
<title>ZVOL</title>
<g id="a_node10"><a xlink:href="#zvol" xlink:title="ZVOL">
<polygon fill="none" points="483,-471 424,-471 424,-435 483,-435 483,-471" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="453.5" y="-449.3">ZVOL</text>
</a>
</g>
</g>
<!-- /dev/zvol/...->ZVOL -->
<g class="edge" id="edge7">
<title>/dev/zvol/...->ZVOL</title>
<path d="M429.575,-506.8314C433.1102,-498.8771 437.336,-489.369 441.2456,-480.5723" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="444.4532,-481.9729 445.3163,-471.4133 438.0565,-479.1299 444.4532,-481.9729" stroke="#000000"></polygon>
</g>
<!-- DSL -->
<g class="node" id="node11">
<title>DSL</title>
<g id="a_node11"><a xlink:href="#dsl" xlink:title="DSL">
<polygon fill="none" points="557.5,-543 503.5,-543 503.5,-507 557.5,-507 557.5,-543" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="530.5" y="-521.3">DSL</text>
</a>
</g>
</g>
<!-- /dev/zfs ioctl->DSL -->
<g class="edge" id="edge10">
<title>/dev/zfs ioctl->DSL</title>
<path d="M614.5895,-525C598.9983,-525 583.4071,-525 567.8159,-525" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="567.5703,-521.5001 557.5703,-525 567.5703,-528.5001 567.5703,-521.5001" stroke="#000000"></polygon>
</g>
<!-- VDEV -->
<g class="node" id="node20">
<title>VDEV</title>
<g id="a_node20"><a xlink:href="#vdev" xlink:title="VDEV">
<polygon fill="none" points="546,-108 487,-108 487,-72 546,-72 546,-108" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="516.5" y="-86.3">VDEV</text>
</a>
</g>
</g>
<!-- /dev/zfs ioctl->VDEV -->
<g class="edge" id="edge29">
<title>/dev/zfs ioctl->VDEV</title>
<path d="M689.2656,-506.9623C696.0025,-479.8344 707.5,-426.8236 707.5,-381 707.5,-381 707.5,-381 707.5,-234 707.5,-186.6526 686.8759,-174.3079 650.5,-144 623.0063,-121.0926 584.5356,-106.9458 555.8638,-98.9099" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="556.7296,-95.5186 546.165,-96.3181 554.9223,-102.2812 556.7296,-95.5186" stroke="#000000"></polygon>
</g>
<!-- DMU -->
<g class="node" id="node13">
<title>DMU</title>
<g id="a_node13"><a xlink:href="#dmu" xlink:title="DMU">
<polygon fill="none" points="555.5,-399 501.5,-399 501.5,-363 555.5,-363 555.5,-399" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="528.5" y="-377.3">DMU</text>
</a>
</g>
</g>
<!-- NFS/CIFS vop_rwlock->DMU -->
<g class="edge" id="edge15">
<title>NFS/CIFS vop_rwlock->DMU</title>
<path d="M285.6968,-509.563C314.41,-500.7092 348.5163,-487.9433 376.5,-471 396.6786,-458.7825 395.9728,-448.2337 415.5,-435 439.4387,-418.7766 469.0038,-404.835 491.8877,-395.1832" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="493.3442,-398.3685 501.2501,-391.3152 490.6713,-391.8989 493.3442,-398.3685" stroke="#000000"></polygon>
</g>
<!-- ZAP -->
<g class="node" id="node12">
<title>ZAP</title>
<g id="a_node12"><a xlink:href="#zap" xlink:title="ZAP">
<polygon fill="none" points="555.5,-471 501.5,-471 501.5,-435 555.5,-435 555.5,-471" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="528.5" y="-449.3">ZAP</text>
</a>
</g>
</g>
<!-- ZPL->ZAP -->
<g class="edge" id="edge8">
<title>ZPL->ZAP</title>
<path d="M367.5472,-469.1304C396.6265,-484.3281 444.0019,-502.8552 483.5,-489 490.6397,-486.4955 497.4881,-482.3291 503.5943,-477.7013" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="505.9521,-480.293 511.4185,-471.2173 501.4855,-474.9032 505.9521,-480.293" stroke="#000000"></polygon>
</g>
<!-- ZPL->DMU -->
<g class="edge" id="edge12">
<title>ZPL->DMU</title>
<path d="M367.7478,-440.7581C372.3191,-438.7848 377.0292,-436.8006 381.5,-435 418.7731,-419.9886 462.0037,-404.312 491.8902,-393.7414" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="493.2174,-396.9848 501.4861,-390.3607 490.8913,-390.3825 493.2174,-396.9848" stroke="#000000"></polygon>
</g>
<!-- ZIL -->
<g class="node" id="node15">
<title>ZIL</title>
<g id="a_node15"><a xlink:href="#zil" xlink:title="ZIL">
<polygon fill="none" points="480.5,-327 426.5,-327 426.5,-291 480.5,-291 480.5,-327" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="453.5" y="-305.3">ZIL</text>
</a>
</g>
</g>
<!-- ZPL->ZIL -->
<g class="edge" id="edge16">
<title>ZPL->ZIL</title>
<path d="M354.8115,-434.7623C374.5947,-409.5518 410.4107,-363.9103 433.0226,-335.0952" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="435.8847,-337.1173 439.3047,-327.0896 430.3778,-332.7959 435.8847,-337.1173" stroke="#000000"></polygon>
</g>
<!-- ZVOL->DMU -->
<g class="edge" id="edge13">
<title>ZVOL->DMU</title>
<path d="M472.4257,-434.8314C481.4857,-426.1337 492.4809,-415.5783 502.3265,-406.1265" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="504.7904,-408.613 509.5804,-399.1628 499.9426,-403.5633 504.7904,-408.613" stroke="#000000"></polygon>
</g>
<!-- DSL->ZAP -->
<g class="edge" id="edge9">
<title>DSL->ZAP</title>
<path d="M529.9953,-506.8314C529.7814,-499.131 529.5271,-489.9743 529.2894,-481.4166" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="532.7879,-481.3122 529.0115,-471.4133 525.7906,-481.5066 532.7879,-481.3122" stroke="#000000"></polygon>
</g>
<!-- DSL->DMU -->
<g class="edge" id="edge14">
<title>DSL->DMU</title>
<path d="M545.614,-506.7768C552.8549,-496.8586 560.7524,-483.9858 564.5,-471 568.9365,-455.6274 569.1365,-450.3135 564.5,-435 561.5436,-425.2355 556.1748,-415.6229 550.47,-407.2762" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="553.2129,-405.0973 544.4905,-399.0831 547.5586,-409.2239 553.2129,-405.0973" stroke="#000000"></polygon>
</g>
<!-- ZAP->DMU -->
<g class="edge" id="edge11">
<title>ZAP->DMU</title>
<path d="M528.5,-434.8314C528.5,-427.131 528.5,-417.9743 528.5,-409.4166" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="532.0001,-409.4132 528.5,-399.4133 525.0001,-409.4133 532.0001,-409.4132" stroke="#000000"></polygon>
</g>
<!-- ARC -->
<g class="node" id="node14">
<title>ARC</title>
<g id="a_node14"><a xlink:href="#arc" xlink:title="ARC">
<polygon fill="none" points="555.5,-327 501.5,-327 501.5,-291 555.5,-291 555.5,-327" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="528.5" y="-305.3">ARC</text>
</a>
</g>
</g>
<!-- DMU->ARC -->
<g class="edge" id="edge17">
<title>DMU->ARC</title>
<path d="M528.5,-362.8314C528.5,-355.131 528.5,-345.9743 528.5,-337.4166" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="532.0001,-337.4132 528.5,-327.4133 525.0001,-337.4133 532.0001,-337.4132" stroke="#000000"></polygon>
</g>
<!-- MetaSlab -->
<g class="node" id="node19">
<title>MetaSlab</title>
<g id="a_node19"><a xlink:href="#metaslab" xlink:title="MetaSlab">
<polygon fill="none" points="641,-180 556,-180 556,-144 641,-144 641,-180" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="598.5" y="-158.3">MetaSlab</text>
</a>
</g>
</g>
<!-- DMU->MetaSlab -->
<g class="edge" id="edge23">
<title>DMU->MetaSlab</title>
<path d="M555.6383,-368.3974C585.2203,-352.9755 630.9457,-323.7894 650.5,-283 663.3726,-256.1483 661.6355,-243.6173 650.5,-216 646.1775,-205.2796 638.5956,-195.4983 630.5361,-187.2948" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="632.7442,-184.5652 623.0801,-180.2228 627.927,-189.644 632.7442,-184.5652" stroke="#000000"></polygon>
</g>
<!-- ZIO -->
<g class="node" id="node16">
<title>ZIO</title>
<g id="a_node16"><a xlink:href="#zio" xlink:title="ZIO">
<polygon fill="none" points="555.5,-252 501.5,-252 501.5,-216 555.5,-216 555.5,-252" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="528.5" y="-230.3">ZIO</text>
</a>
</g>
</g>
<!-- ARC->ZIO -->
<g class="edge" id="edge20">
<title>ARC->ZIO</title>
<path d="M528.5,-290.8446C528.5,-282.3401 528.5,-272.0076 528.5,-262.4964" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="532.0001,-262.2481 528.5,-252.2482 525.0001,-262.2482 532.0001,-262.2481" stroke="#000000"></polygon>
</g>
<!-- L2ARC -->
<g class="node" id="node17">
<title>L2ARC</title>
<g id="a_node17"><a xlink:href="#l2arc" xlink:title="L2ARC">
<polygon fill="none" points="641.5,-252 573.5,-252 573.5,-216 641.5,-216 641.5,-252" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="607.5" y="-230.3">L2ARC</text>
</a>
</g>
</g>
<!-- ARC->L2ARC -->
<g class="edge" id="edge21">
<title>ARC->L2ARC</title>
<path d="M554.9972,-290.9209C558.2877,-288.3716 561.5341,-285.6973 564.5,-283 572.1249,-276.0657 579.7661,-267.8645 586.4472,-260.1753" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="589.4807,-262.0092 593.2739,-252.1166 584.1396,-257.4845 589.4807,-262.0092" stroke="#000000"></polygon>
</g>
<!-- ZIL->ZIO -->
<g class="edge" id="edge19">
<title>ZIL->ZIO</title>
<path d="M480.869,-292.5464C484.9519,-289.5928 488.9697,-286.3728 492.5,-283 499.3026,-276.501 505.7319,-268.5881 511.2235,-261.0336" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="514.3671,-262.6436 517.2054,-252.436 508.6211,-258.6456 514.3671,-262.6436" stroke="#000000"></polygon>
</g>
<!-- SLOG -->
<g class="node" id="node18">
<title>SLOG</title>
<g id="a_node18"><a xlink:href="#slog" xlink:title="SLOG">
<polygon fill="none" points="483,-252 424,-252 424,-216 483,-216 483,-252" stroke="#0000ff"></polygon>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="453.5" y="-230.3">SLOG</text>
</a>
</g>
</g>
<!-- ZIL->SLOG -->
<g class="edge" id="edge18">
<title>ZIL->SLOG</title>
<path d="M453.5,-290.8446C453.5,-282.3401 453.5,-272.0076 453.5,-262.4964" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="457.0001,-262.2481 453.5,-252.2482 450.0001,-262.2482 457.0001,-262.2481" stroke="#000000"></polygon>
</g>
<!-- ZIO->MetaSlab -->
<g class="edge" id="edge24">
<title>ZIO->MetaSlab</title>
<path d="M546.164,-215.8314C554.4732,-207.2848 564.5264,-196.9443 573.5918,-187.6198" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="576.1369,-190.023 580.5982,-180.4133 571.1179,-185.1435 576.1369,-190.023" stroke="#000000"></polygon>
</g>
<!-- ZIO->VDEV -->
<g class="edge" id="edge28">
<title>ZIO->VDEV</title>
<path d="M526.9802,-215.7623C524.9334,-191.201 521.2706,-147.2474 518.8628,-118.3541" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="522.3259,-117.7644 518.0075,-108.0896 515.3501,-118.3458 522.3259,-117.7644" stroke="#000000"></polygon>
</g>
<!-- L2ARC->ZIO -->
<g class="edge" id="edge22">
<title>L2ARC->ZIO</title>
<path d="M573.4775,-234C570.8786,-234 568.2797,-234 565.6807,-234" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="565.6563,-230.5001 555.6563,-234 565.6562,-237.5001 565.6563,-230.5001" stroke="#000000"></polygon>
</g>
<!-- L2ARC->VDEV -->
<g class="edge" id="edge26">
<title>L2ARC->VDEV</title>
<path d="M581.9503,-215.8625C570.0905,-206.3385 556.6295,-193.7883 547.5,-180 535.0104,-161.137 527.0138,-136.7121 522.2764,-118.0676" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="525.6279,-117.032 519.9132,-108.1105 518.8171,-118.6485 525.6279,-117.032" stroke="#000000"></polygon>
</g>
<!-- SLOG->VDEV -->
<g class="edge" id="edge25">
<title>SLOG->VDEV</title>
<path d="M461.479,-215.7623C472.3192,-190.9846 491.7938,-146.4714 504.4277,-117.5939" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="507.7841,-118.6541 508.5858,-108.0896 501.371,-115.8483 507.7841,-118.6541" stroke="#000000"></polygon>
</g>
<!-- MetaSlab->VDEV -->
<g class="edge" id="edge27">
<title>MetaSlab->VDEV</title>
<path d="M577.8079,-143.8314C567.8052,-135.0485 555.645,-124.3712 544.8001,-114.8489" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="547.0092,-112.1308 537.1854,-108.1628 542.3906,-117.3909 547.0092,-112.1308" stroke="#000000"></polygon>
</g>
<!-- physical storage devices -->
<g class="node" id="node21">
<title>physical storage devices</title>
<path d="M613,-32.7273C613,-34.5331 569.7472,-36 516.5,-36 463.2528,-36 420,-34.5331 420,-32.7273 420,-32.7273 420,-3.2727 420,-3.2727 420,-1.4669 463.2528,0 516.5,0 569.7472,0 613,-1.4669 613,-3.2727 613,-3.2727 613,-32.7273 613,-32.7273" fill="none" stroke="#000000"></path>
<path d="M613,-32.7273C613,-30.9214 569.7472,-29.4545 516.5,-29.4545 463.2528,-29.4545 420,-30.9214 420,-32.7273" fill="none" stroke="#000000"></path>
<text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="516.5" y="-14.3">physical storage devices</text>
</g>
<!-- VDEV->physical storage devices -->
<g class="edge" id="edge30">
<title>VDEV->physical storage devices</title>
<path d="M516.5,-71.8314C516.5,-64.131 516.5,-54.9743 516.5,-46.4166" fill="none" stroke="#000000"></path>
<polygon fill="#000000" points="520.0001,-46.4132 516.5,-36.4133 513.0001,-46.4133 520.0001,-46.4132" stroke="#000000"></polygon>
</g>
</g>
</svg>
<p>接下來從底層往上介紹一下各個子系統的全稱和職能。</p>
</div>
<div class="section" id="spa">
<h2><a class="toc-backref" href="#id6">SPA</a></h2>
<p>Storage Pool Allocator</p>
<p>從內核提供的多個塊設備中抽象出存儲池的子系統。 SPA 進一步分爲 ZIO 和 VDEV 兩大部分和其餘一些小的子系統。</p>
<p>SPA 對 DMU 提供的接口不同於傳統的塊設備接口,完全利用了 CoW 文件系統對寫入位置不敏感的特點。
傳統的塊設備接口通常是寫入時指定一個寫入地址,把緩衝區寫到磁盤指定的位置上,而 DMU 可以讓 SPA
做兩種操作:</p>
<ol class="arabic simple">
<li><code class="code">
write</code>
, DMU 交給 SPA 一個數據塊的內存指針, SPA
負責找設備寫入這個數據塊,然後返回給 DMU 一個 block pointer 。</li>
<li><code class="code">
read</code>
,DMU 交給 SPA 一個 block pointer ,SPA 讀取設備並返回給 DMU
完整的數據塊。</li>
</ol>
<p>也就是說,在 DMU 讓 SPA 寫數據塊時, DMU 還不知道 SPA 會寫入的地方,這完全由 SPA 判斷,
這一點通常被稱爲 Write Anywhere ,在別的 CoW 文件系統比如 Btrfs 和 WAFL 中也有這個特點。
反過來 SPA 想要對一個數據塊操作時,也完全不清楚這個數據塊用於什麼目的,屬於什麼文件或者文件系統結構。</p>
</div>
<div class="section" id="vdev">
<h2><a class="toc-backref" href="#id7">VDEV</a></h2>
<p>Virtual DEVice</p>
<p>VDEV 在 ZFS 中的作用相當於 Linux 內核的 Device Mapper 層或者 FreeBSD GEOM 層,提供
Stripe/Mirror/RAIDZ 之類的多設備存儲池管理和抽象。 ZFS 中的 vdev
形成一個樹狀結構,在樹的底層是從內核提供的物理設備,
其上是虛擬的塊設備。每個虛擬塊設備對上對下都是塊設備接口,除了底層的物理設備之外,位於中間層的
vdev 需要負責地址映射、容量轉換等計算過程。</p>
<p>除了用於存儲數據的 Stripe/Mirror/RAIDZ 之類的 VDEV ,還有一些特殊用途的 VDEV
,包括提供二級緩存的 L2ARC 設備,以及提供 ZIL 高速日誌的 SLOG 設備。</p>
</div>
<div class="section" id="zio">
<h2><a class="toc-backref" href="#id8">ZIO</a></h2>
<div class="panel panel-default">
<div class="panel-heading">
ZIO Pipeline by George Wilson</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/qkA5HhfzsvM"></iframe></div></div>
</div>
<p>ZFS I/O</p>
<p>作用相當於內核的 IO scheduler 和 pagecache write back 機制。
OpenZFS Summit 有个演讲整理了 ZIO 流水线的工作原理。
ZIO 內部使用流水線和事件驅動機制,避免讓上層的 ZFS 線程阻塞等待在 IO 操作上。
ZIO 把一個上層的寫請求轉換成多個寫操作,負責把這些寫操作合併到
transaction group 提交事務組。 ZIO 也負責將讀寫請求按同步還是異步分成不同的讀寫優先級並實施優先級調度,
在 <a class="reference external" href="https://github.com/zfsonlinux/zfs/wiki/ZIO-Scheduler">OpenZFS 項目 wiki 頁有一篇描述 ZIO 調度</a>
的細節。</p>
<p>除了調度之外, ZIO 層還負責在讀寫流水線中拆解和拼裝數據塊。上層 DMU 交給 SPA 的數據塊有固定大小,
目前默認是 128KiB ,pool 整體的參數可以調整塊大小在 4KiB 到 8MiB 之間。ZIO
拿到整塊大小的數據塊之後,在流水線中可以對數據塊做諸如以下操作:</p>
<ol class="arabic simple">
<li>用壓縮算法,壓縮/解壓數據塊。</li>
<li>查詢 dedup table ,對數據塊去重。</li>
<li>加密/解密數據塊。</li>
<li>計算數據塊的校驗和。</li>
<li>如果底層分配器不能分配完整的 128KiB (或 zpool 設置的大小),那麼嘗試分配多個小塊,然後用多個
512B 的指針間接塊連起多個小塊的,拼裝成一個虛擬的大塊,這個機制叫
<a class="reference external" href="https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSGangBlocks">gang block</a>
。通常 ZFS 中用到 gang block 時,整個存儲池處於極度空間不足的情況,由 gang block
造成嚴重性能下降,而 gang block 的意義在於在空間接近要滿的時候也能 CoW
寫入一些元數據,釋放亟需的存儲空間。</li>
</ol>
<p>可見經過 ZIO 流水線之後,數據塊不再是統一大小,這使得 ZFS 用在 4K 對齊的磁盤或者 SSD
上有了一些新的挑戰。</p>
</div>
<div class="section" id="metaslab">
<h2><a class="toc-backref" href="#id9">MetaSlab</a></h2>
<div class="panel panel-default">
<div class="panel-heading">
MetaSlab Allocation Performance by Paul Dagnelie</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/LZpaTGNvalE"></iframe></div></div>
</div>
<p>MetaSlab 是 ZFS 的塊分配器。 VDEV 把存儲設備抽象成存儲池之後, MetaSlab
負責實際從存儲設備上分配數據塊,跟蹤記錄可用空間和已用空間。</p>
<p>叫 MetaSlab 這個名字是因爲 Jeff 最初同時也給 Solaris 內核寫過
<a class="reference external" href="https://en.wikipedia.org/wiki/Slab_allocation">slab 分配器</a>
,一開始設計 SPA 的時候 Jeff 想在 SPA 中也利用 Solaris 的 slab
分配器對磁盤空間使用類似的分配算法。後來 MetaSlab 逐漸不再使用 slab 算法,只有名字留了下來。</p>
<p>MetaSlab 的結構很接近於 FreeBSD UFS 的 cylinder group ,或者 ext2/3/4 的 block group
,或者 xfs 的 allocation group ,目的都是讓存儲分配策略「局域化」,
或者說讓近期分配的數據塊的物理地址比較接近。在存儲設備上創建 zpool
的時候,首先會儘量在存儲設備上分配 200 個左右的 MetaSlab ,隨後給 zpool
增加設備的話使用接近的 MetaSlab 大小。每個 MetaSlab 是連續的一整塊空間,在 MetaSlab
內對數據塊空間做分配和釋放。磁盤中存儲的 MetaSlab 的分配情況是按需載入內存的,系統
import zpool 時不需要載入所有 MetaSlab 到內存,而只需加載一小部分。當前載入內存的 MetaSlab
剩餘空間告急時,會載入別的 MetaSlab 嘗試分配,而從某個 MetaSlab 釋放空間不需要載入 MetaSlab
。</p>
<p>OpenZFS Summit 也有一個對 MetaSlab 分配器性能的介紹,可以看到很多分配器內的細節。</p>
</div>
<div class="section" id="arc">
<h2><a class="toc-backref" href="#id10">ARC</a></h2>
<div class="panel panel-default">
<div class="panel-heading">
ELI5: ZFS Caching Explain Like I'm 5: How the ZFS Adaptive Replacement Cache works</div>
<div class="panel-body">
<div align="left" class="youtube embed-responsive embed-responsive-16by9"><iframe allow="fullscreen" class="embed-responsive-item" frameborder="0" src="https://www.youtube.com/embed/1Wo3i2gkAIk"></iframe></div></div>
</div>
<p>Adaptive Replacement Cache</p>
<p>ARC 的作用相當於 Linux/Solaris/FreeBSD 中傳統的 page/buffer cache 。
和傳統 pagecache 使用 LRU (Least Recently Used) 之類的算法剔除緩存頁不同, ARC
算法試圖在 LRU 和 LFU(Least Frequently Used) 之間尋找平衡,從而複製大文件之類的線性大量
IO 操作不至於讓緩存失效率猛增。最近 FOSDEM 2019 有一個介紹 ZFS ARC 工作原理的視頻。</p>
<p>不過 ZFS 採用它自有的 ARC 一個顯著缺點在於,不能和內核已有的 pagecache 機制相互配合,尤其在
系統內存壓力很大的情況下,內核與 ZFS 無關的其餘部分可能難以通知 ARC 釋放內存。所以 ARC
是 ZFS 消耗內存的大戶之一(另一個是可選的 dedup table),也是
<a class="reference external" href="http://open-zfs.org/wiki/Performance_tuning#Adaptive_Replacement_Cache">ZFS 性能調優</a>
的重中之重。</p>
<p>當然, ZFS 採用 ARC 不依賴於內核已有的 pagecache 機制除了 LFU 平衡的好處之外,也有別的有利的一面。
系統中多次讀取因 snapshot 或者 dedup 而共享的數據塊的話,在 ZFS 的 ARC 機制下,同樣的
block pointer 只會被緩存一次;而傳統的 pagecache 因爲基於 inode 判斷是否有共享,
所以即使這些文件有共享頁面(比如 btrfs/xfs 的 reflink 形成的),也會多次讀入內存。 Linux
的 btrfs 和 xfs 在 VFS 層面有共用的 reflink 機制之後,正在努力着手改善這種局面,而 ZFS
因爲 ARC 所以從最初就避免了這種浪費。</p>
<p>和很多傳言所說的不同, ARC 的內存壓力問題不僅在 Linux 內核會有,在 FreeBSD 和
Solaris/Illumos 上也是同樣,這個在
<a class="reference external" href="https://youtu.be/xMH5rCL8S2k?t=997">ZFS First Mount by Mark Shellenbaum 的問答環節 16:37 左右有提到</a>
。其中 Mark Shellenbaum 提到 Matt 覺得讓 ARC 併入現有 pagecache
子系統的工作量太大,難以實現。</p>
<p>因爲 ARC 工作在 ZIO 上層,所以 ARC 中緩存的數據是經過 ZIO
從存儲設備中讀取出來之後解壓、解密等處理之後的,原始的數據。最近 ZFS 的版本有支持一種新特性叫
<a class="reference external" href="https://www.illumos.org/issues/6950">Compressed ARC</a>
,打破 ARC 和 VDEV 中間 ZIO 的壁壘,把壓縮的數據直接緩存在 ARC
中。這麼做是因爲壓縮解壓很快的情況下,壓縮的 ARC 能節省不少內存,讓更多數據保留在 ARC
中從而提升緩存利用率,並且在有 L2ARC 的情況下也能增加 L2ARC 能存儲的緩存。</p>
</div>
<div class="section" id="l2arc">
<h2><a class="toc-backref" href="#id11">L2ARC</a></h2>
<p>Level 2 Adaptive Replacement Cache</p>
<p>這是用 ARC 算法實現的二級緩存,保存於高速存儲設備上。常見用法是給 ZFS pool 配置一塊 SSD
作爲 L2ARC 高速緩存,減輕內存 ARC 的負擔並增加緩存命中率。</p>
</div>
<div class="section" id="slog">
<h2><a class="toc-backref" href="#id12">SLOG</a></h2>
<p>Separate intent LOG</p>
<p>SLOG 是額外的日誌記錄設備。 SLOG 之於 ZIL 有點像 L2ARC 之餘 ARC , L2ARC 是把內存中的
ARC 放入額外的高速存儲設備,而 SLOG 是把原本和別的數據塊存儲在一起的 ZIL
放到額外的高速存儲設備。</p>
</div>
<div class="section" id="tol">
<h2><a class="toc-backref" href="#id13">TOL</a></h2>
<p>Transactional Object Layer</p>
<p>這一部分子系統在數據塊的基礎上提供一個事務性的對象語義層,這裏事務性是指,
對對象的修改處於明確的狀態,不會因爲突然斷電之類的原因導致狀態不一致。TOL
中最主要的部分是 DMU 層。</p>
</div>
<div class="section" id="dmu">
<h2><a class="toc-backref" href="#id14">DMU</a></h2>
<p>Data Management Unit</p>
<p>在塊的基礎上提供「對象(object)」的抽象。每個「對象」可以是一個文件,或者是別的 ZFS 內部需要記錄的東西。</p>
<p>DMU 這個名字最初是 Jeff 想類比於操作系統中內存管理的 MMU(Memory Management Unit),
Jeff 希望 ZFS 中增加和刪除文件就像內存分配一樣簡單,增加和移除塊設備就像增加內存一樣簡單,
由 DMU 負責從存儲池中分配和釋放數據塊,對上提供事務性語義,管理員不需要管理文件存儲在什麼存儲設備上。
這裏事務性語義指對文件的修改要麼完全成功,要麼完全失敗,不會處於中間狀態,這靠 DMU 的 CoW
語義實現。</p>
<p>DMU 實現了對象級別的 CoW 語義,從而任何經過了 DMU 做讀寫的子系統都具有了 CoW 的特徵,
這不僅包括文件、文件夾這些 ZPL 層需要的東西,也包括文件系統內部用的 spacemap 之類的設施。
相反,不經過 DMU 的子系統則可能沒法保證事務語義。這裏一個特例是 ZIL ,一定程度上繞過了 DMU
直接寫日誌。說一定程度是因爲 ZIL 仍然靠 DMU 來擴展長度,當一個塊寫滿日誌之後需要等 DMU
分配一個新塊,在分配好的塊內寫日誌則不需要經過 DMU 。所有經過 DMU 子系統的對象都有 CoW
語義,也意味着 ZFS 中不能對某些文件可選地關閉 CoW ,不能提供數據庫應用的 direct IO 之類的接口。</p>
<p>「對象(object)」抽象是 DMU 最重要的抽象,一個對象的大小可變,佔用一個或者多個數據塊(
默認一個數據塊 128KiB )。上面提到 SPA 的時候也講了 DMU 和 SPA 之間不同於普通塊設備抽象的接口,這使得 DMU
按整塊的大小分配空間。當對象使用多個數據塊存儲時, DMU 提供間接塊(indirect block)來引用這些數據塊。
間接塊很像傳統 Unix 文件系統(Solaris UFS 或者 Linux ext2)中的一級二級三級間接塊,
一個間接塊存儲很多塊指針(block pointer),多個間接塊形成樹狀結構,最終一個塊指針可以引用到一個對象。
更現代的文件系統比如 ext4/xfs/btrfs/ntfs 提供了 extent 抽象,可以指向一個連續範圍的存儲塊,
而 ZFS 不使用類似 extent 的抽象。DMU 採用間接塊而不是 extent
,使得 ZFS 的空間分配更趨向碎片化,爲了避免碎片化造成的性能影響,需要儘量延遲寫入使得一次寫入能在磁盤上
儘量連續,這裏 ARC 提供的緩存和 ZIO 提供的流水線對延遲寫入避免碎片有至關重要的幫助。</p>
<p>有了「對象(object)」的抽象之後, DMU 進一步實現了「對象集(objectset)」的抽象,
一個對象集中保存一系列按順序編號的 dnode ( ZFS 中類似 inode 的數據結構),每個 dnode 有足夠空間
指向一個對象的最多三個塊指針,如果對象需要更多數據塊可以使用間接塊,如果對象很小也可以直接壓縮進
dnode 。隨後 DSL 又進一步用對象集來實現數據集(dataset)抽象,提供比如文件系統(filesystem
)、快照(snapshot)、克隆(clone)之類的抽象。一個對象集中的對象可以通過 dnode 編號相互引用,
就像普通文件系統的硬鏈接引用 inode 編號那樣。</p>
<p>上面也提到因爲 SPA 和 DMU 分離, SPA 完全不知道數據塊用於什麼目的;這一點其實對 DMU 也是類似,
DMU 雖然能從某個對象找到它所佔用的數據塊,但是 DMU 完全不知道這個對象在文件系統或者存儲池中是
用來存儲什麼的。當 DMU 讀取數據遇到壞塊(block pointer 中的校驗和與 block pointer
指向的數據塊內容不一致)時,它知道這個數據塊在哪兒(具體哪個設備上的哪個地址),
但是不知道這個數據塊是否和別的對象共享,不知道搬動這個數據塊的影響,也沒法從對象反推出文件系統路徑,
(除了明顯開銷很高地掃一遍整個存儲池)。所以 DMU 在遇到讀取錯誤(普通的讀操作或者 scrub/resilver
操作中)時,只能選擇在同樣的地址,原地寫入數據塊的備份(如果能找到或者推算出備份的話)。</p>
<p>或許有人會疑惑,既然從 SPA 無法根據數據地址反推出對象,在 DMU 也無法根據對象反推出文件,那麼
zfs 在遇到數據損壞時是如何在診斷信息中給出損壞的文件路徑的呢?這其實基於 ZPL 的一個黑魔法:
<a class="reference external" href="https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSPathLookupTrick">在 dnode 記錄父級 dnode 的編號</a>
。因爲是個黑魔法,這個記錄不總是對的,所以只能用於診斷信息,不能基於這個實現別的文件系統功能。</p>
</div>
<div class="section" id="zap">
<h2><a class="toc-backref" href="#id15">ZAP</a></h2>
<p>ZFS Attribute Processor</p>
<p>在 DMU 提供的「對象」抽象基礎上提供緊湊的 name/value 映射存儲,
從而文件夾內容列表、文件擴展屬性之類的都是基於 ZAP 來存。 ZAP 在內部分爲兩種存儲表達:
microZAP 和 fatZAP 。</p>
<p>一個 microZAP 佔用一整塊數據塊,能存 name 長度小於 50 字符並且 value 是 uint64_t 的表項,
每個表項 64 字節。 <del>fatZAP 則是個樹狀結構,能存更多更複雜的東西。</del><ins>fatZAP 是個 on disk 的散利表,指針表中是 64bit 對 name 的 hash ,指向單鏈表的子節點列表,子節點中的 value 可以是任意類型的數據(不光是 uint64_t )。</ins></p>
<p>可見 microZAP 非常適合表述一個普通大小的文件夾裏面包含到很多普通文件 inode (ZFS 是 dnode)的引用。
<del></del><ins>fatZAP 則不光可以用於任意大小的文件夾,還可以表達 ZFS 的配置屬性之類的東西,非常靈活。</ins></p>
<p>在 <a class="reference external" href="https://youtu.be/xMH5rCL8S2k?t=526">ZFS First Mount by Mark Shellenbaum 的8:48左右</a>
提到,最初 ZPL 中關於文件的所有屬性(包括訪問時間、權限、大小之類所有文件都有的)都是基於
ZAP 來存,也就是說每個文件都有個 ZAP ,其中有叫做 size 呀 owner
之類的鍵值對,就像是個 JSON 對象那樣,這讓 ZPL 一開始很容易設計原型並且擴展。然後文件夾內容列表有另一種數據結構
ZDS(ZFS Directory Service),後來常見的文件屬性在 ZPL 有了專用的緊湊數據結構,而 ZDS 則漸漸融入了 ZAP 。
<del></del><ins>這些變化詳見下面 ZPL 。</ins></p>
</div>
<div class="section" id="dsl">
<h2><a class="toc-backref" href="#id16">DSL</a></h2>
<p>Dataset and Snapshot Layer</p>
<p>數據集和快照層,負責創建和管理快照、克隆等數據集類型,跟蹤它們的寫入大小,最終刪除它們。
由於 DMU 層面已經負責了對象的寫時複製語義和對象集的概念,所以 DSL 層面不需要直接接觸寫文件之類來自 ZPL
的請求,無論有沒有快照對 DMU 層面一樣採用寫時複製的方式修改文件數據。
不過在刪除快照和克隆之類的時候,則需要 DSL 參與計算沒有和別的數據集共享的數據塊並且刪除它們。</p>
<p>DSL 管理數據集時,也負責管理數據集上附加的屬性。ZFS 每個數據集有個屬性列表,這些用 ZAP 存儲,
DSL 則需要根據數據集的上下級關係,計算出繼承的屬性,最終指導 ZIO 層面的讀寫行爲。</p>
<p>除了管理數據集, DSL 層面也提供了 zfs 中 send/receive 的能力。 ZFS 在 send 時從 DSL
層找到快照引用到的所有數據塊,把它們直接發往管道,在 receive 端則直接接收數據塊並重組數據塊指針。
因爲 DSL 提供的 send/receive 工作在 DMU 之上,所以在 DSL 看到的數據塊是 DMU
的數據塊,下層 SPA 完成的數據壓縮、加密、去重等工作,對 DMU 層完全透明。所以在最初的
send/receive 實現中,假如數據塊已經壓縮,需要在 send 端經過 SPA 解壓,再 receive
端則重新壓縮。最近 ZFS 的 send/receive 逐漸打破 DMU 與 SPA
的壁壘,支持了直接發送已壓縮或加密的數據塊的能力。</p>
</div>
<div class="section" id="zil">
<h2><a class="toc-backref" href="#id17">ZIL</a></h2>
<p>ZFS Intent Log</p>
<p>記錄兩次完整事務語義提交之間的日誌,用來加速實現 fsync 之類的文件事務語義。</p>
<p>原本 CoW 的文件系統不需要日誌結構來保證文件系統結構的一致性,在 DMU
保證了對象級別事務語義的前提下,每次完整的 transaction group commit
都保證了文件系統一致性,掛載時也直接找到最後一個 transaction group 從它開始掛載即可。
不過在 ZFS 中,做一次完整的 transaction group commit 是個比較耗時的操作,
在寫入文件的數據塊之後,還需要更新整個 object set ,然後更新 meta-object set
,最後更新 uberblock ,爲了滿足事務語義這些操作沒法並行完成,所以整個 pool
提交一次需要等待好幾次磁盤寫操作返回,短則一兩秒,長則幾分鐘,
如果事務中有要刪除快照等非常耗時的操作可能還要等更久,在此期間提交的事務沒法保證一致。</p>
<p>對上層應用程序而言,通常使用 fsync 或者 fdatasync 之類的系統調用,確保文件內容本身的事務一致性。
如果要讓每次 fsync/fdatasync 等待整個 transaction group commit
完成,那會嚴重拖慢很多應用程序,而如果它們不等待直接返回,則在突發斷電時沒有保證一致性。
從而 ZFS 有了 ZIL ,記錄兩次 transaction group 的 commit 之間發生的 fsync
,突然斷電後下次 import zpool 時首先找到最近一次 transaction group ,在它基礎上重放
ZIL 中記錄的寫請求和 fsync 請求,從而滿足 fsync API 要求的事務語義。</p>
<p>顯然對 ZIL 的寫操作需要繞過 DMU 直接寫入數據塊,所以 ZIL 本身是以日誌系統的方式組織的,每次寫
ZIL 都是在已經分配的 ZIL 塊的末尾添加數據,分配新的 ZIL 塊仍然需要經過 DMU
的空間分配。</p>
<p>傳統日誌型文件系統中對 data 開啓日誌支持會造成每次文件系統寫入操作需要寫兩次到設備上,
一次寫入日誌,再一次覆蓋文件系統內容;在
ZIL 實現中則不需要重複寫兩次, DMU 讓 SPA 寫入數據之後 ZIL 可以直接記錄新數據塊的
block pointer ,所以使用 ZIL 不會導致傳統日誌型文件系統中雙倍寫入放大的問題。</p>
</div>
<div class="section" id="zvol">
<h2><a class="toc-backref" href="#id18">ZVOL</a></h2>
<p>ZFS VOLume</p>
<p>有點像 loopback block device ,暴露一個塊設備的接口,其上可以創建別的
FS 。對 ZFS 而言實現 ZVOL 的意義在於它是比文件更簡單的接口,所以在實現完整 ZPL
之前,一開始就先實現了 ZVOL ,而且
<a class="reference external" href="https://youtu.be/xMH5rCL8S2k?t=298">早期 Solaris 沒有 thin provisioning storage pool 的時候可以用 ZVOL 模擬很大的塊設備,當時 Solaris 的 UFS 團隊用它來測試 UFS 對 TB 級存儲的支持情況</a>
。</p>
<p>因爲 ZVOL 基於 DMU 上層,所以 DMU 所有的文件系統功能,比如 snapshot / dedup / compression
都可以用在 ZVOL 上,從而讓 ZVOL 上層的傳統文件系統也具有類似的功能。並且 ZVOL 也具有了 ARC
緩存的能力,和 dedup 結合之下,非常適合於在一個宿主機 ZFS
上提供對虛擬機文件系統鏡像的存儲,可以節省不少存儲空間和內存佔用開銷。</p>
</div>
<div class="section" id="zpl">
<h2><a class="toc-backref" href="#id19">ZPL</a></h2>
<p>ZFS Posix Layer</p>
<p>提供符合 POSIX 文件系統語義的抽象,也就是包括文件、目錄、軟鏈接、套接字這些抽象以及
inode 訪問時間、權限那些抽象,ZPL 是 ZFS 中對一個普通 FS 而言用戶直接接觸的部分。
ZPL 可以說是 ZFS 最複雜的子系統,也是 ZFS 作爲一個文件系統而言最關鍵的部分。</p>
<p>ZPL 的實現中直接使用了 ZAP 和 DMU 提供的抽象,比如每個 ZPL 文件用一個 DMU 對象表達,每個
ZPL 目錄用一個 ZAP 對象表達,然後 DMU 對象集對應到 ZPL 下的一個文件系統。
也就是說 ZPL 負責把操作系統 VFS 抽象層的那些文件系統操作接口,翻譯映射到基於 DMU 和 ZAP
的抽象上。傳統 Unix 中的管道、套接字、軟鏈接之類的沒有什麼數據內容的東西則在 ZPL 直接用 dnode
實現出來。 ZPL 也需要進一步實現文件權限、所有者、訪問日期、擴展屬性之類雜七雜八的文件系統功能。</p>
<div class="label label-warning">
<strong>2020年2月9日添加</strong></div>
<p>繼續上述 ZAP 格式變化的討論,在 ZPL 拋棄早期用 ZAP 的設計之後, ZPL 中 znode (ZPL 擴展的 dnode)
保存文件屬性的機制成爲了一個小的子系統,叫
<a class="reference external" href="https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/fs/zfs/sa.c">ZFS System Attributes</a>
。 SA 的設計照顧了舊版 ZPL znode 格式兼容問題,有新舊兩代格式。舊版 znode
格式是固定偏移位置存取屬性的 SA ,因此透過預先註冊好的描述舊版 znode 格式的固定映射表,
SA 依然能用同樣的代碼路徑存取舊版的 znode 。而後來
<a class="reference external" href="https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSSystemAttributes">靈活的新設計下的 SA 更有意思</a>
,ZFS 認識到,大部分 znode 的屬性都可以用有限的幾種屬性集來表达,
比如普通文件有一組類似的屬性(權限、所有者之類的), zvol 有另一組(明顯 zvol 不需要很多 ZPL
文件的屬性),整個 ZFS dataset 可以「註冊」幾種屬性佈局,然後讓每個 znode 引用其中一種佈局,
這樣 znode 保存的屬性仍然是可以任意變化的,又不需要在每個 znode 中都記錄所有屬性的名字。
SA 的出現提升了 ZPL 的可擴展性。 ZPL 爲了應付不同的操作系統之間文件系統 API 的差異,可以使用
SA 在 znode 之中加入針對不同操作系統和應用場景的屬性。例如,在支持 NFSv4 ACL 的操作系統上,ZFS
既可以用現有方式把 DACL ACEs 放在獨立於文件對象的單獨對象中,也可以把 DACL ACEs 放在 SA 內。</p>
<p><a class="reference external" href="https://youtu.be/xMH5rCL8S2k?t=456">在 ZFS First Mount by Mark Shellenbaum</a>
中介紹了很多在最初實現 ZPL 過程中的坎坷, ZPL 的困難之處在於需要兼容現有應用程序對傳統文件系統
API 的使用方式,所以他們需要大量兼容性測試。視頻中講到非常有意思的一件事是, ZFS
在設計時不想重複 Solaris UFS 設計中的很多缺陷,於是實現 VFS API 時有諸多取捨和再設計。
其中他們遇到了 <code class="code">
VOP_RWLOCK</code>
,這個是 UFS 提供的文件級別讀寫鎖。對一些應用尤其是
NFS 而言,文件讀寫鎖能保證應用層的一致性,而對另一些應用比如數據庫而言,
文件鎖的粒度太大造成了性能問題。在設計 ZPL 的時候他們不想在 ZFS 中提供 <code class="code">
VOP_RWLOCK</code>
,這讓 NFS 開發者們很難辦(要記得 NFS 也是 Solaris 對 Unix 世界貢獻的一大發明)。
最終 ZFS 把 DMU 的內部細節也暴露給了 NFS ,讓 NFS 基於 DMU 的對象創建時間( TXG id
)而不是文件鎖來保證 NFS 的一致性。結果是現在 ZFS 中也有照顧 NFS 的代碼,後來也加入了
Samba/CIFS 的支持,從而在 ZFS 上設置 NFS export 時是通過 ZFS 的機制而非系統原生的 NFS
export 機制。</p>
</div>