ZFS 的读写流程
本文参考 http://open-zfs.org/wiki/Documentation/Read_Write_Lecture 而成.
---
ZFS 的特色
存储池 (文件系统与卷管理二合一) 支持事务的对象模型 (no fsck) 全流程数据完整性 (验证 hash 并纠正错数据, 注: 纠正仅限有副本/冗余. 默认 fletcher4, 可选 sha256. 但开 dedup 时强烈推荐 sha256.) 管理简单 可伸缩
---

左边是传统文件系统的路径, 右边圆角框内是 ZFS 实现的部分. 传统模式下文件系统只知道有一个固定大小的块设备, 不能知道块设备是否给了完全正确的数据, 也不知道冗余情况. ZFS 重新设计了相关的逻辑.
DMU: Data Management Unit 大概相当于 CPU 的 MMU (映射虚拟/物理内存地址).
SPA: Storage Pool Allocator 分配并释放存储空间 (注: 显然 SPA 是每个 pool 一个) 如果要往文件系统做写操作: 1 数据会经过 DMU 2 DMU 对 SPA 说: 这里有 8 KB 数据, 写到磁盘某处然后告诉我下次怎么取回 (块指针) 3 SPA 寻找一个空闲位置并分配之 (并考虑 RAID 等), 返回存储地址.
ZPL: ZFS POSIX Layer ZPL - DMU 接口给文件系统提供类 POSIX 的用法支持, 操作 DMU, 存储的是 nodeid -> object.
ZVOL: ZFS Volume, 相当于一个大文件做块设备.
---
ZAP: ZFS Attribute Processor, 磁盘上的 kvs, 存目录. ZFS 是 CoW 文件系统 (注: 注意不是日志型!) 1 写到空闲空间 2 写完了一块数据, 需要一层层写祖先节点直到顶层 (注: 类似 merkle tree 动了叶节点) 3 这个操作很麻烦 (用 ZIL 解决) ZIL: ZFS Intent Log 用于加速同步操作的持久化过程. ARC: Adaptive Replacement Cache (注意: 可以对 dataset 选择开关)
---
zfs_read 流程 1 zfs_range_lock POSIX 要求 caller 视角下读写是原子性的. - writer 写一块区域: 要么全改写, 或者表现为原状. - 其他很多文件系统只有文件级的读写锁 (大意: 自求多福吧, 并发写同一个区间当然会出事的) 有了 range lock (区间锁), 就可以检测并阻塞并发的重叠区间写, 并发读是不会阻塞的. 数据结构是 AVL 树.
2 dmu_read_uio_dbuf(zdb, uio, nbytes) -> dmu_buf_hold_array_by_dnode ZFS 支持多种逻辑块大小(recordsize/volblocksize for zvol), 和不同的物理块大小(传说中的 ashift=12 问题), 逻辑块默认 128 KB (注: ARC 替换操作的粒度就是逻辑块. 只有对小于逻辑块大小的文件的操作才会出现大量小块, 否则小块最多每文件一个). 较大的 IO 当然可以切分并行完成. -> zio_root() 其实是个空的啥也不做. 但是可以挂上子 zio 形成一个树, 然后发射出去 (zio_wait 会等待所有子 zio 完成). -> 对每个块做 dbuf_hold() -> dbuf_read(), 然后挂在 zio_root 上. -> dbuf_read(), dbuf_read_impl() -> arc_read(zio, os_spa, db_blkptr) -> zio_read(), 仅限 ARC 未命中
zio_read() 流程 -> zio_create() -> zio_wait() -> zio_execute() -> zio_vdev_io_start() (其它函数略)
---
在说明 write 流程之前补充背景知识.
ZFS transaction groups (也可以称传输组. 简称 txg). 详情见 txg.c 注释. txg 有开放, 停顿, 同步三种状态. 脏数据先写入开放态的 txg (缓存在 DMU 里). 每 5 秒或者脏数据量达到阈值后 txg 停止接受数据(相当于 checkpoint), 进入停顿态. 并开放下一个 txg 供写入. 如果同步态的 txg 完成了写入, 则拿下一个停顿态的 txg 去同步. (思考题: 什么时候应该限速?)
支持事务的对象模型由 dmu_tx 实现. 理论上完全可以利用 zfs 的事务引擎写 db 之类的. 操作方法参见 zfs_vnops.c 注释, 简化版如下:
//拿锁 tx = dmu_tx_create() dmu_tx_hold_*() // 先通报每一个要改的项目 error = dmu_tx_assign(tx, ...) // 等一个开放的 txg if (error) { //解锁, 重试的部分简化了 dmu_tx_abort(tx); return error; } error = do_real_work(); //实际操作 if (error == 0) zfs_log_*(...); // 写入 ZIL dmu_tx_commit(tx); //解锁 zil_commit(zilog, foid); return error;
---
zfs_write -> zfs_range_lock() (写锁) -> dmu_tx_create() 见上 -> dmu_tx_assign() -> dmu_tx_wait() 会检查 dirty data 的总量, 可能会调用 dmu_tx_delay(). -> 写入有两种路径. 每块有两种情况(应该是与路径无关?). 部分写(读出整个块, 修改, 置为 dirty) 或者全覆盖写(无需读原来的数据) -> dmu_write_uio_dbuf(), 或者 dmu_assign_arcbuf() (-> zfs_log_write() 在这里, 能够决定 immediate / indirect 写入方式, SLOG/ZIL 相关) (-> dmu_tx_commit(), 同简化版) -> zil_commit() (仅限 sync 时)
注意, 此时数据在 DMU / ARC 里 (sync 模式下会立刻发出 IO 请求). hash 也于此时生成. 关于 ECC 内存的要求, 要点是如果 txg 写入前发生位翻转将无法检测 (*: 对非 ECC 机可以开启 ZFS_DEBUG_MODIFY). 写入的路径会调用 dbuf_dirty(), 这样会建立 dirty records 并挂上 dnode.
---
txg_sync_thread 是内核线程, 作用类似于 writeback. 如有停顿态 txg 即调用 spa_sync() 开始同步. 这里的 zio_write 使用的是 async_write 优先级, 写入一定是线性的(见下).
spa_sync(spa, txg) 1-> dsl_pool_sync() 遍历脏数据集合 2 -> dsl_dataset_sync() 3 -> dmu_objset_sync() 4 -> dmu_objset_sync_dnodes() 这里写的是普通文件的数据 5 -> dnode_sync() 6 -> dbuf_sync_list() 7 -> dbuf_sync_leaf (写数据) -> dbuf_write -> zio_write/arc_write 7 -> dbuf_sync_indirect (写祖先, 注意上面所说的 CoW 问题) 3 -> zio_nowait() 并行发射. 不等待返回
---
ZFS IO Scheduler 是依照请求 priority 分级存放的, 也是 AVL 结构实现 (vdev_queue.c). 每个 vdev 有若干个 queue, 优先级从高到低 (但 prio 变量是小而高) 分别是: sync_read, sync_write(主要是 ZIL), async_read(=prefetch), async_write(=txg writeback), scrub (=resilver) (去掉了不常用的).
每一个 queue 都有 min_active 和 max_active, 即限制并发数. max_active 总和相当于 nr_requests. 首先保证每个 queue 的 min_active 并发数. 如果有低优先级的 queue 未满 min_active 可以被高优先的抢占, 超过 min_active 之后整个队列只会有同优先级请求(即完全不能抢占).
async_write 比较特殊是可变比例的 (因为容易挤压), 通常只保证 min_active (lustre 推荐从 2 加到 5). 其 max_active 只有 zfs_vdev_async_write_active_max_dirty_percent 取到的时候才能达到.
调度器根据 active 空闲量, 优先级从高到低确定下一个 IO 来自哪个优先级的队列, 从那个队列取头任务. 队列的排序问题, sync_read/write 的队列是 FIFO (相当于 deadline). 其它队列都是 LBA 即偏移量排序的 (容易形成顺序 IO, 算简化的 elevator, 简化是因为 只有一个方向).
---
本文未完, 缺 ZAP operations.
-
王家传 赞了这篇日记 2019-11-23 20:43:31
-
plusthinky 赞了这篇日记 2018-03-14 19:38:52
-
到此一游 e/acc 赞了这篇日记 2018-03-14 16:14:20
-
BohuTANG 赞了这篇日记 2018-03-14 16:11:50
-
slo 赞了这篇日记 2018-03-13 12:57:53
-
AlbertJi 赞了这篇日记 2018-03-12 18:04:33
-
fzieng 赞了这篇日记 2018-03-12 18:02:28
-
sky快跑 赞了这篇日记 2018-03-12 17:22:42