从存储引擎到文件系统:用FUSE将分布式KV挂载为本地目录

本文使用fuse将其与分布式的kv项目进行融合。实现了一个简易的文件系统,目的是为了学习和理解Fuse的框架和工作原理。仍然存在许多局限性的问题等待改善,作者在最后一章节会提到,希望各位大佬多多指正

1 介绍

用户态文件系统(filesystem in userspace, 简称FUSE),它能使用户在无需编辑和编译内核代码 的情况下,创建用户自定义的文件系统。文件系统是操作系统的重要组成部分,一般在内核层面实现对于文件系统的支持,而通常内核态的代码难以调试,生产率较低。在用户态空间实现文件系统能够极大幅度的提高生产效率,简化为实现新的文件系统的工作量。FUSE主要包含两个部分,内核FUSE模块(Linux 从2.6.14版本开始支持)和用户态Libfuse库。

2 项目概述

一个基于 FUSE(Filesystem in Userspace)框架和分布式 KV 存储实现的分布式文件系统。该系统允许用户通过标准的文件系统命令(如 lscatecho 等)来操作分布式数据库中的数据,实现了文件系统接口 与分布式存储系统的无缝集成。

2.1 技术栈

  • Fuse3:用户空间文件系统框架
  • RESP协议:Redis序列化协议,用于客户端与服务器通信
  • TCP/IP:网络通信协议
  • 分布式kv系统:基于Raft算法的分布式kv存储系统

2.2 功能架构

简单而言,即可以在文件系统中通过标准的文件系统命令去操作分布式存储集群的这样一个功能

2.3 核心功能映射

文件系统操作 FUSE 函数 KV 操作 说明
ls jl_readdir GET __FILE_LIST__ 获取文件列表
cat file.txt jl_read GET file.txt 读取文件内容
echo "content" > file.txt jl_write SET file.txt content 写入文件
touch file.txt jl_create SET file.txt "" 创建空文件
rm file.txt jl_unlink DELETE file.txt 删除文件
stat file.txt jl_getattr EXIST file.txt 获取文件属性

3 核心实现原理

3.1 网络通信模块

3.1.1 连接管理

cpp 复制代码
static int connect_to_server(const char *ip, int port) {
    // 1. 创建 TCP socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 2. 设置服务器地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &server_addr.sin_addr);
    server_addr.sin_port = htons(port);
    
    // 3. 建立连接
    connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
    return fd;
}

原理:使用标准的TCP socket编程接口建立与分布式kv存储服务器的连接。支持自动重连和Leader节点发现

3.1.2 RESP协议实现

RESP(Redis Serialization Protocol)是一种简单的二进制安全协议,用于客户端与服务器之间的通信。协议格式如下:

cpp 复制代码
*<参数个数>\r\n
$<参数1长度>\r\n
<参数1内容>\r\n
$<参数2长度>\r\n
<参数2内容>\r\n
...
cpp 复制代码
static char *build_resp_command(const char *cmd, int argc, char **argv) {
    static char buffer[MAX_BUFFER_SIZE];
    int pos = 0;
    
    // 构建数组头
    pos += snprintf(buffer + pos, sizeof(buffer) - pos, "*%d\r\n", argc + 1);
    
    // 添加命令
    pos += snprintf(buffer + pos, sizeof(buffer) - pos, "$%zu\r\n%s\r\n", strlen(cmd), cmd);
    
    // 添加参数
    for (int i = 0; i < argc; i++) {
        pos += snprintf(buffer + pos, sizeof(buffer) - pos, "$%zu\r\n%s\r\n", strlen(argv[i]), argv[i]);
    }
    
    return buffer;
}

3.1.3 响应解析

RESP 协议支持多种响应类型:

类型 前缀 示例 说明
简单字符串 + +OK\r\n 成功响应
错误 - -Error message\r\n 错误响应
整数 : :100\r\n 整数值
批量字符串 $ $6\r\nfoobar\r\n 二进制安全字符串
数组 * *2\r\n... 数组

3.2 KV操作封装

3.2.1 基本操作

cpp 复制代码
// 获取键值
static int kv_get(const char *key, char *value, size_t value_size) {
    char *argv[] = {(char*)key};
    char *command = build_resp_command("RCGET", 1, argv);
    return send_command(command, 0, value, value_size);
}

// 设置键值
static int kv_set(const char *key, const char *value) {
    char *argv[] = {(char*)key, (char*)value};
    char *command = build_resp_command("RCSET", 2, argv);
    char response[MAX_BUFFER_SIZE];
    return send_command(command, 1, response, sizeof(response));
}

// 删除键
static int kv_delete(const char *key) {
    char *argv[] = {(char*)key};
    char *command = build_resp_command("RCDELETE", 1, argv);
    char response[MAX_BUFFER_SIZE];
    return send_command(command, 1, response, sizeof(response));
}

// 检查键是否存在
static int kv_exist(const char *key) {
    char *argv[] = {(char*)key};
    char *command = build_resp_command("RCEXIST", 1, argv);
    char response[MAX_BUFFER_SIZE];
    if (send_command(command, 0, response, sizeof(response)) == 0) {
        return atoi(response);
    }
    return 0;
}

说明:使用RC系列命令(RCSET,RCGET,RCCOUNT,RCEXIST)与分布式kv存储交互

3.2.2 文件列表管理

由于 KV 存储只提供键值对操作,我们需要一种机制来跟踪所有文件。解决方案是使用一个特殊的键 __FILE_LIST__ 来存储文件列表。

存储格式file1|file2|file3

cpp 复制代码
static int add_to_file_list(const char *filename) {
    char list[MAX_BUFFER_SIZE] = {0};
    kv_get(file_list_key, list, sizeof(list));
    
    // 检查是否已存在
    if (file_exists_in_list(list, filename)) {
        return 0;
    }
    
    // 添加到列表
    if (strlen(list) > 0) {
        strcat(list, "|");
    }
    strcat(list, filename);
    
    return kv_set(file_list_key, list);
}

4 Fuse文件系统操作

4.1 获取文件属性(getattr)

cpp 复制代码
static int jl_getattr(const char *path, struct stat *st, struct fuse_file_info *fi) {
    memset(st, 0, sizeof(struct stat));
    
    // 根目录
    if (strcmp(path, "/") == 0) {
        st->st_mode = S_IFDIR | 0755;
        st->st_nlink = 2;
        return 0;
    }
    
    // 普通文件
    const char *filename = path + 1;
    if (kv_exist(filename)) {
        st->st_mode = S_IFREG | 0644;
        st->st_nlink = 1;
        
        // 获取文件大小
        char content[MAX_BUFFER_SIZE] = {0};
        if (kv_get(filename, content, sizeof(content)) == 0) {
            st->st_size = strlen(content);
        }
        
        return 0;
    }
    
    return -ENOENT;
}

原理

  1. 判断路径是否为根目录
  2. 如果是文件,检查kv存储中是否存在
  3. 如果存在,设置文件属性(类型、权限、大小等)

4.2 读取目录(readdir)

cpp 复制代码
static int jl_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                      off_t offset, struct fuse_file_info *fi,
                      enum fuse_readdir_flags flags) {
    if (strcmp(path, "/") != 0) {
        return -ENOENT;
    }
    
    // 填充标准目录项
    filler(buf, ".", NULL, 0, (enum fuse_fill_dir_flags)0);
    filler(buf, "..", NULL, 0, (enum fuse_fill_dir_flags)0);
    
    // 获取文件列表
    FileInfo files[MAX_FILES];
    int count = get_file_list(files, MAX_FILES);
    
    // 填充文件项
    for (int i = 0; i < count; i++) {
        filler(buf, files[i].name, NULL, 0, (enum fuse_fill_dir_flags)0);
    }
    
    return 0;
}

原理

  1. 从KV存储获取文件列表
  2. 使用filler函数填充目录项
  3. 返回给FUSE框架

4.3 读取文件(read)

cpp 复制代码
static int jl_read(const char *path, char *buf, size_t size, off_t offset,
                   struct fuse_file_info *fi) {
    const char *filename = path + 1;
    
    // 从 KV 存储获取文件内容
    char content[MAX_BUFFER_SIZE] = {0};
    if (kv_get(filename, content, sizeof(content)) != 0) {
        return -ENOENT;
    }
    
    // 计算读取范围
    size_t len = strlen(content);
    if (offset >= (off_t)len) {
        return 0;
    }
    
    if (offset + size > len) {
        size = len - offset;
    }
    
    // 复制数据到缓冲区
    memcpy(buf, content + offset, size);
    return size;
}

原理

  1. 从 KV 存储读取完整文件内容
  2. 根据 offset 和 size 返回指定范围的数据
  3. 支持随机读取

4.4 写入文件(write)

cpp 复制代码
static int jl_write(const char *path, const char *buf, size_t size, off_t offset,
                    struct fuse_file_info *fi) {
    const char *filename = path + 1;
    
    // 读取现有内容
    char content[MAX_BUFFER_SIZE] = {0};
    kv_get(filename, content, sizeof(content));
    
    // 写入新数据
    if (offset + size >= MAX_BUFFER_SIZE) {
        return -ENOSPC;
    }
    
    memcpy(content + offset, buf, size);
    content[offset + size] = '\0';
    
    // 保存到 KV 存储
    if (kv_set(filename, content) != 0) {
        return -EIO;
    }
    
    // 更新文件列表
    add_to_file_list(filename);
    
    return size;
}

原理

  1. 读取现有文件内容
  2. 在指定偏移量写入新的数据
  3. 讲更新后的内容保存到kv存储
  4. 更新文件列表

4.5 创建文件(create)

cpp 复制代码
static int jl_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
    const char *filename = path + 1;
    
    // 创建空文件
    if (kv_set(filename, "") != 0) {
        return -EIO;
    }
    
    // 添加到文件列表
    add_to_file_list(filename);
    
    return 0;
}

4.6 删除文件(unlink)

cpp 复制代码
static int jl_unlink(const char *path) {
    const char *filename = path + 1;
    
    // 检查文件是否存在
    if (!kv_exist(filename)) {
        return -ENOENT;
    }
    
    // 从 KV 存储删除
    if (kv_delete(filename) != 0) {
        return -EIO;
    }
    
    // 从文件列表移除
    remove_from_file_list(filename);
    
    return 0;
}

5 局限性

5.1 当前局限

  1. 文件大小限制 :单个文件最大 64KB(MAX_BUFFER_SIZE
  2. 文件数量限制 :最多 1024 个文件(MAX_FILES
  3. 不支持目录:只支持单层目录结构
  4. 无并发控制:多个客户端同时操作可能导致数据不一致
  5. 无持久化缓存:每次操作都需要网络请求

5.2 改进反向

  1. 支持大文件:将大文件分块存储
  2. 支持多级目录:使用路径作为键的一部分
  3. 添加分布式锁:使用 Raft 实现分布式锁服务
  4. 实现客户端缓存:减少网络请求
  5. 支持文件权限:存储文件权限信息
相关推荐
song5016 小时前
对话:模型推理慢,怎么调
人工智能·分布式·深度学习·transformer·交互
LB21126 小时前
消灭并发重复调用:基于 Agent 调用 LLM 的分布式 Single-Flight 实战
java·开发语言·redis·分布式·agent
心中有国也有家7 小时前
ascend-boost-comm:一次写完,到处复用——算子公共平台的 M×N 哲学
人工智能·经验分享·笔记·分布式·算法
jameslogo8 小时前
RocketMQ与Kafka零拷贝机制
分布式·kafka·rocketmq
500848 小时前
GE 怎么做算子融合
分布式·架构·开源·wpf
楠枬10 小时前
Redis 分布式锁
数据库·redis·分布式
心中有国也有家12 小时前
hixl:昇腾分布式推理的「快递专线」
人工智能·经验分享·笔记·分布式·学习·算法
不爱编程的小陈1 天前
探究raft的线性一致性读方法
分布式
devnullcoffee1 天前
亚马逊Browse Node类目树数据采集实战:从PA-API到分布式爬虫
分布式·爬虫·亚马逊数据采集 api·亚马逊类目树数据·亚马逊 browse node·amazon 数据 api