一.3FS提供两种方式写数据
使用标准POSIX接口的方式,则所有读写调用会通过VFS和FUSE转发给3FS客户端,客户端负责处理数据传输:
以下主要是讲3FS高性能API的方式.
二.通过高性能API write()写请求流程
1.用户进程调用3FS的高性能API(如usrbio_alloc)分配Iov内存,然后将数据写入Iov
2.用户进程调用write(fd, buf, count),其中buf指向Iov中的数据地址
3.VFS收到wirte()请求后,将请求转到FUSE 内核模块
4.FUSE内核模块 将写请求发送给用户态fuse客户端
5.FUSE客户端 收到请求后,直接通过共享内存获取iov上的数据
6.FUSE客户端 与存储节点之间已经通过 InfiniBand Verbs API建立了 RDMA 队列对QP用于通信
7.FUSE客户端 使用 RDMA写操作(RDMA_WRITE)将 Iov 中的数据发送到存储节点
8.存储节点的 RDMA 适配器接收到写请求后,直接将数据写入指定的内存区域。
9.存储节点的软件层随后将数据从内存持久化到存储介质
10.RDMA 写操作完成后,FUSE客户端 收到完成通知
11.FUSE客户端 通过FUSE内核模块 将写操作的成功状态通知用户进程
三.设计的优势
3FS采用“应用写入Iov → FUSE客户端从Iov读取 → RDMA从Iov传输到存储节点”的流程,有以下优势:
-
零拷贝:应用数据写入Iov后,FUSE客户端直接读取,无需额外拷贝。
-
高效传输:FUSE客户端通过RDMA将Iov数据发送到存储节点,充分利用硬件加速。
-
统一管理:FUSE客户端负责所有RDMA操作,简化应用开发,优化系统资源。
四.为什么不让应用直接通过RDMA传输?
1.文件系统功能的必要性
-
文件系统不仅仅是数据存储,还包括命名空间管理、权限控制、数据一致性和元数据处理。如果应用直接通过RDMA发送数据到存储节点,会绕过这些功能,可能导致:
- 数据一致性问题:多个应用可能同时写入同一位置,造成冲突。
- 权限问题:应用可能访问未授权的数据,增加安全风险。
-
FUSE客户端确保所有操作符合文件系统规则,维护数据完整性。
2.分布式存储的复杂性
-
3FS是一个分布式文件系统,存储节点可能分布在多个物理位置,涉及负载均衡、数据复制和故障恢复。
-
如果应用直接通过RDMA传输,需要知道存储节点的地址、端口和数据分布策略,这对应用来说过于复杂。
-
FUSE客户端负责这些管理任务,应用只需通过标准接口操作文件,简化使用。
3.性能与资源管理的优化
-
RDMA操作需要内存注册(通过ibv_reg_mr),这有一定开销。如果每个应用都直接使用RDMA,可能会导致资源竞争和注册开销增加。
-
FUSE客户端可以预先注册共享内存Iov,供多个应用复用,减少重复开销。
-
此外,FUSE客户端能批量处理I/O请求,优化RDMA队列对(Queue Pair, QP)的使用,提升整体性能。
4.标准接口与兼容性
-
3FS通过FUSE提供POSIX接口,让应用可以用标准系统调用(如write())操作文件,无需修改代码。
-
如果应用直接通过RDMA传输,需要使用InfiniBand Verbs API或类似库,这会增加开发复杂性,且不兼容现有应用程序。
-
从3FS用户空间API文档来看,3FS的设计目标是易用性与高性能兼顾。
5.安全性和访问控制
-
FUSE客户端可以根据文件权限限制访问,防止未授权操作。
-
如果应用直接通过RDMA发送数据,可能绕过这些安全机制,增加数据泄露或篡改风险。
五.Example
#include <hf3fs_usrbio.h> // 3FS用户态I/O库头文件
// 定义I/O参数:1024次I/O操作,每个块32MB
constexpr uint64_t NUM_IOS = 1024;
constexpr uint64_t BLOCK_SIZE = (32 << 20);
int main() {
// 创建I/O请求上下文 (I/O Request)
struct hf3fs_ior ior;
hf3fs_iorcreate4(&ior, // 输出参数:I/O请求上下文
"/hf3fs/mount/point", // 挂载点路径
NUM_IOS, // 最大并发I/O数
true, // 是否启用异步模式
0, 0, // 保留参数
-1, // NUMA节点(-1表示自动选择)
0); // 标志位
// 创建I/O向量管理 (I/O Vector)
struct hf3fs_iov iov;
hf3fs_iovcreate2(&iov, // 输出参数:I/O向量管理
"/hf3fs/mount/point", // 挂载点路径
NUM_IOS * BLOCK_SIZE, // 总预分配空间
0, // 对齐要求
-1, // NUMA节点
nullptr); // 回调函数
// 打开文件并注册到3FS
int fd = open("/hf3fs/mount/point/example.bin", O_RDONLY);
hf3fs_reg_fd(fd, 0); // 注册文件描述符到3FS(0表示默认选项)
// 准备批量读取请求
for (int i = 0; i < NUM_IOS; i++) {
hf3fs_prep_io(&ior, // I/O请求上下文
&iov, // I/O向量管理
true, // 是否读取操作(false为写入)
iov.base + i * BLOCK_SIZE, // 目标内存地址
fd, // 文件描述符
i * BLOCK_SIZE, // 文件偏移量
BLOCK_SIZE, // 传输长度
nullptr); // 回调函数
}
// 提交所有I/O请求到存储系统
hf3fs_submit_ios(&ior);
// 等待所有I/O完成
hf3fs_cqe cqes[NUM_IOS]; // 完成事件数组
hf3fs_wait_for_ios(&ior, // I/O请求上下文
cqes, // 输出完成事件
NUM_IOS, // 最小完成数
NUM_IOS, // 最大完成数
nullptr); // 超时设置
// 清理资源
hf3fs_dereg_fd(fd); // 注销文件描述符
close(fd); // 关闭文件
hf3fs_iovdestroy(&iov); // 销毁I/O向量
hf3fs_iordestroy(&ior); // 销毁I/O请求上下文
return 0;
}