peface

做nas的时候, 需要将本地文件系统到远程分布式系统, 一种基于 block 的思路, 另一种基于 fuse 去做. 大都数情况下是基于 fuse.

abstraction

fuse的设计, 就是将内核文件系统的操作委托给 user space 的 custom daemon 进程, 比如: create/delete/open/read/write 以及 元数据操作: getattr/link/unlink/forget 等.

在使用上, 有两种api 可以使用, 一种是 low-level的, 需要自己设计 recv/protocol parse/reply/config/mout 等行为, 以及 屏蔽内核的差异; 另一种是 high-level的, developer 可以不用关心 path->inode 的映射, 用户只需要实现相关的方法就行: chown、chmod、truncate.

底层实现

在fuse底层的实现上, 应用程序读写文件系统, 透传cache的请求, VFS 会委托给 fuse driver, fuse 和 fuse user space daemon 通过 /dev/fuse 文件进行交互 (论文图片). 在 fuse driver->fuse user daemon 交互过程中, 维护了多个queue: interrupts/forgets/pending/processing/background, 如下:

来两个图

一个request 在任意时刻只会属于一个queue.

同步的请求(metadata) 先放在 pendig queue, fuse user daemon 从 /dev/fuse 读取进行处理的时候, request 会被迁移到 processing 队列, fuse user-daemon 响应的时候, 直接到 processing 匹配并移除. 一些特殊的请求, 比如 forget 请求直接进入 forget 队列, interrupt请求 进入 interrupt 队列, 上面这些队列都是同步的; 部分请求比如: read/write(write 只有在 writeback cache被开启的时候才会进入background, writeback 就是说用户的写请求先积攒在page cache 知道 pdflush 被唤醒刷新脏页) 直接进入 backgrounding 队列, 后面再进入 pending 队列, 需要注意, 这里为了约束异步请求阻塞同步请求, 有参数: max background, 限制从异步队列进入pending队列的数量

除此之外, VFS 也通过 congestion threshold 限流上层用户请求 (一般是75% * all async request)

几个队列存在优先级: interrupt request 需要被立即处理; forget 和 non-forget request 按照1:2 的概率公平选择 (避免forget尖刺 & 请他请求饥饿). 没有请求的时候, fuse user daemon 则会阻塞.

重要特性(优化)

  1. splice(zero-copy): 为了避免 fuse user-daemon 和 fuse driver 因为读写 /dev/fuse 导致的 kernel/user-space 频繁的数据拷贝, 这里利用了 kernle 提供的 splice 的 zero-copy的特性

  2. 多线程支持: 实现了 最多10个 idle threads 的pool 处理, 如果超过10个异步请求, 会有相应的线程处理, 但是处理完之后会退出, 创建没有上界

  3. writeback cache& max write: 本质上是为了提升每次IO的大小实现更高的iops

测试实现&压测

论文中基于fuse试下了 stackfs, 将fuse请求转发给本地盘的 ext4 上, 观察不同操作类型的性能损失. 其中还对比了 基于重要特性的 opt 版本.

从结果上看, 性能损耗还是比较明显的, rand 场景下, 最多可以损失达到 80%+, 因为 rand 场景下, 多线程&writeback 并不能真正发挥威力, 并且多线程场景下 还存在锁的开销.

参考