暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Curve 文件存储——FUSE 开发入门及性能调优实践

OpenCurve 2023-06-21
605

前言

Curve 是云原生计算基金会 (CNCF) Sandbox 项目,是网易主导自研和开源的高性能、易运维、云原生的分布式存储系统。

本文首先简要介绍下 FUSE 文件系统以及 FUSE 文件系统的开发实践,接着会介绍开发 FUSE 文件系统后关于性能优化方面的一些内容。

FUSE 文件系统简介
在内核下开发文件系统不仅难度较高,而且不易调试,并且一旦出问题还会导致系统崩溃,因此用户空间文件系统就应运而生了。用户空间中的文件系统 (FUSE) 是操作系统的软件接口,允许非特权用户创建自己的文件系统而无需编辑内核代码。这是通过在用户空间中运行文件系统代码来实现的,而 FUSE 模块仅提供到实际内核接口的桥梁。

FUSE 文件系统开发实践 - "hello world"

基础
FUSE 文件系统的开发简单来讲的话主要为以下几步:
  • 实现 fuse_lowlevel_ops 结构体对应的接口
如下是 fuse_lowlevel_ops 结构体,不需要实现该结构体的所有函数,按需实现相关函数即可:
    struct fuse_lowlevel_ops {
    void (*init) (void *userdata, struct fuse_conn_info *conn);


    void (*destroy) (void *userdata);


    void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);


    void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);


    void (*getattr) (fuse_req_t req, fuse_ino_t ino,
    struct fuse_file_info *fi);


    void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    int to_set, struct fuse_file_info *fi);


    void (*readlink) (fuse_req_t req, fuse_ino_t ino);


    void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
    mode_t mode, dev_t rdev);


    void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
    mode_t mode);
    // .... 还有其他很多接口
    }
    • 创建 fuse session
    把上述重定义的 fuse_lowlevel_ops 作为参数传递给 fuse_session_new 函数创建 session
    • 创建挂载
    调用 fuse_session_mount 创建挂载
    • 工作
    调用 fuse_session_loop 开始准备接收相应消息,并根据请求类型分别调用 fuse_lowlevel_ops 中重定义的函数
    实例
    这里我们用一个简单的 hello world 程序来展示下 FUSE 文件系统的开发:
      #define FUSE_USE_VERSION 34


      #include <fuse_lowlevel.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <errno.h>
      #include <fcntl.h>
      #include <unistd.h>
      #include <assert.h>


      static const char *hello_str = "Hello World!\n";
      static const char *hello_name = "hello";


      // fuse_lowlevel_op接口函数实现
      static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
      {
      stbuf->st_ino = ino;
      switch (ino) {
      case 1:
      stbuf->st_mode = S_IFDIR | 0755;
      stbuf->st_nlink = 2;
      break;


      case 2:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = strlen(hello_str);
      break;


      default:
      return -1;
      }
      return 0;
      }


      // fuse_lowlevel_op接口函数实现
      static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
      struct fuse_file_info *fi)
      {
      struct stat stbuf;


      (void) fi;


      memset(&stbuf, 0, sizeof(stbuf));
      if (hello_stat(ino, &stbuf) == -1)
      fuse_reply_err(req, ENOENT);
      else
      fuse_reply_attr(req, &stbuf, 1.0);
      }


      // fuse_lowlevel_op接口函数实现
      static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
      {
      struct fuse_entry_param e;


      if (parent != 1 || strcmp(name, hello_name) != 0)
      fuse_reply_err(req, ENOENT);
      else {
      memset(&e, 0, sizeof(e));
      e.ino = 2;
      e.attr_timeout = 1.0;
      e.entry_timeout = 1.0;
      hello_stat(e.ino, &e.attr);


      fuse_reply_entry(req, &e);
      }
      }


      struct dirbuf {
      char *p;
      size_t size;
      };


      static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
      fuse_ino_t ino)
      {
      struct stat stbuf;
      size_t oldsize = b->size;
      b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
      b->p = (char *) realloc(b->p, b->size);
      memset(&stbuf, 0, sizeof(stbuf));
      stbuf.st_ino = ino;
      fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
      b->size);
      }


      #define min(x, y) ((x) < (y) ? (x) : (y))


      static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
      off_t off, size_t maxsize)
      {
      if (off < bufsize)
      return fuse_reply_buf(req, buf + off,
      min(bufsize - off, maxsize));
      else
      return fuse_reply_buf(req, NULL, 0);
      }


      // fuse_lowlevel_op接口函数实现
      static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
      off_t off, struct fuse_file_info *fi)
      {
      (void) fi;


      if (ino != 1)
      fuse_reply_err(req, ENOTDIR);
      else {
      // 当发生readdir调用调用时,返回hello_name文件名
      struct dirbuf b;
      memset(&b, 0, sizeof(b));
      dirbuf_add(req, &b, ".", 1);
      dirbuf_add(req, &b, "..", 1);
      dirbuf_add(req, &b, hello_name, 2);
      reply_buf_limited(req, b.p, b.size, off, size);
      free(b.p);
      }
      }


      // fuse_lowlevel_op接口函数实现
      static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
      struct fuse_file_info *fi)
      {
      if (ino != 2)
      fuse_reply_err(req, EISDIR);
      else if ((fi->flags & O_ACCMODE) != O_RDONLY)
      fuse_reply_err(req, EACCES);
      else
      fuse_reply_open(req, fi);
      }


      // fuse_lowlevel_op接口函数实现
      static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
      off_t off, struct fuse_file_info *fi)
      {
      (void) fi;


      assert(ino == 2);
      // 当发生read调用时,返回hello_str中的字符串
      reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
      }


      // fuse_lowlevel_op接口实现
      static const struct fuse_lowlevel_ops hello_ll_oper = {
      .lookup = hello_ll_lookup,
      .getattr = hello_ll_getattr,
      .readdir = hello_ll_readdir,
      .open = hello_ll_open,
      .read = hello_ll_read,
      };


      int main(int argc, char *argv[])
      {
      struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
      struct fuse_session *se;
      struct fuse_cmdline_opts opts;
      struct fuse_loop_config config;
      int ret = -1;
      // 参数解析
      if (fuse_parse_cmdline(&args, &opts) != 0)
      return 1;
      if (opts.show_help) {
      printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
      fuse_cmdline_help();
      fuse_lowlevel_help();
      ret = 0;
      goto err_out1;
      } else if (opts.show_version) {
      printf("FUSE library version %s\n", fuse_pkgversion());
      fuse_lowlevel_version();
      ret = 0;
      goto err_out1;
      }


      if(opts.mountpoint == NULL) {
      printf("usage: %s [options] <mountpoint>\n", argv[0]);
      printf(" %s --help\n", argv[0]);
      ret = 1;
      goto err_out1;
      }
      // 创建fuse session
      se = fuse_session_new(&args, &hello_ll_oper,
      sizeof(hello_ll_oper), NULL);
      if (se == NULL)
      goto err_out1;


      if (fuse_set_signal_handlers(se) != 0)
      goto err_out2;


      // 挂载
      if (fuse_session_mount(se, opts.mountpoint) != 0)
      goto err_out3;


      fuse_daemonize(opts.foreground);


      // 开始工作
      /* Block until ctrl+c or fusermount -u */
      if (opts.singlethread)
      ret = fuse_session_loop(se);
      else {
      config.clone_fd = opts.clone_fd;
      config.max_idle_threads = opts.max_idle_threads;
      ret = fuse_session_loop_mt(se, &config);
      }


      fuse_session_unmount(se);
      err_out3:
      fuse_remove_signal_handlers(se);
      err_out2:
      fuse_session_destroy(se);
      err_out1:
      free(opts.mountpoint);
      fuse_opt_free_args(&args);


      return ret ? 1 : 0;
      }
      • 编译
      gcc -Wall hello.c pkg-config fuse  --cflags --libs  -o hello
      • 挂载以及使用
        // 创建挂载目录
        root@hostname:~$ mkdir mountpoint
        // 挂载
        root@hostname:~$ ./hello mountpoint
        // 演示
        root@hostname:~$ cd mountpoint
        root@hostname:~/mountpoint$
        root@hostname:~/mountpoint$ ls
        hello
        root@hostname:~/mountpoint$ cat hello
        Hello World!


        // 卸载 - 卸载失败,因为挂载点被占用
        root@hostname:~/mountpoint$ sudo umount ~/mountpoint
        umount: home/root/mountpoint: target is busy
        (In some cases useful info about processes that
        use the device is found by lsof(8) or fuser(1).)
        // 退出挂载点目录
        root@hostname:~/mountpoint$ cd
        // 卸载 - 成功
        root@hostname:~$ sudo umount ~/mountpoint

        性能迷雾

        前面简单介绍了下 FUSE 的一些基本原理,接下来让我们聊聊性能优化相关的一些问题。在 fio 测试过程中,发现带宽一直上不去,通过分析 client 相关日志(或通过 systenmtap 等工具)可以发现不管 fio 工具设置的 bs size 多大,到达 FUSE 应用文件系统的 io 最多也只有 128KB,所以推测在内核层做了 io 拆分。同时由于内核层有文件锁带来的 io 串行机制,所以带宽能力会收到限制。关于文件锁,这里敲下小黑板,画个小重点吧:22 年 6 月份,社区加入了一个新 feature 去支持单文件的并发写,详情见单文件支持并发写
        单文件支持并发写:https://lore.kernel.org/lkml/20220605072201.9237-1-dharamhans87@gmail.com/t/
        那其实这个问题在业内是已经由来已久的问题了,可以追溯到上古时代的上古 issue, 摘录一段如下:
        上古 issue:https://lkml.org/lkml/2012/7/5/136
          This patch series make maximum read/write request size tunable in FUSE.
          Currently, it is limited to FUSE_MAX_PAGES_PER_REQ which is equal
          to 32 pages. It is required to change it in order to improve the
          throughput since optimized value depends on various factors such
          as type and version of local filesystems used and HW specs, etc.


          In addition, recently FUSE is widely used as a gateway to connect
          cloud storage services and distributed filesystems. Larger data
          might be stored in them over networking via FUSE and the overhead
          might affect the throughput.


          It seems there were many requests to increase FUSE_MAX_PAGES_PER_REQ
          to improve the throughput,



          问题解决

          从上面的相关 issue 我们可以看到,大家有讨论过修改 FUSE 请求大小这一问题。基于业务方性能方面的要求,我们对此进行了优化。具体代码见 Github FUSE带宽优化
          FUSE 带宽优化:
          https://github.com/wuhongsong/linux/commit/bee0cdf5924f20a96717929cd147e2e5a0c93735
          优化后,我们把 fuse_max_pages_per_req 设置为了可动态修改的配置项,通过以下命令可以动态调整该值:
            root@pubt1-ceph73:/mnt/test1# echo 1024 > sys/module/fuse/parameters/fuse_max_pages_per_req
            该值默认是 32,可以通过把该
            值调大进而提升带宽。通
            过把该值修改为 1024,在我们的 poc 环境下性
            能提升数
            据如下:

            优化前
            优化后
            1
            M 写(带宽)

            70MB/s
            192MB/s
            1M 写(时延)
            233ms
            82ms

            后续
            我们的修改是基于 4.19 内核的,然而,接下来的 4.20 版本就把该配置项的类似修改 merge 到了内核,详情见 
            pr: fuse: add max_pages to init_out

            pr 地址:https://github.com/torvalds/linux/commit/5da784cce4308
              root@hostname:~/github_linux/linux/fs/fuse$ git show 5da784cce4308
              commit 5da784cce4308ae10a79e3c8c41b13fb9568e4e0
              Author: Constantine Shulyupin <const@MakeLinux.com>
              Date: Thu Sep 6 15:37:06 2018 +0300


              fuse: add max_pages to init_out


              Replace FUSE_MAX_PAGES_PER_REQ with the configurable parameter max_pages to
              improve performance.


              Old RFC with detailed description of the problem and many fixes by Mitsuo
              Hayasaka (mitsuo.hayasaka.hu@hitachi.com):
              - https://lkml.org/lkml/2012/7/5/136


              We've encountered performance degradation and fixed it on a big and complex
              virtual environment.


              上述性能调优只是 Curve 文件存储众多性能调优手段的其中之一,后续我们将会继续分享更多的性能优化方案,欢迎感兴趣的小伙伴关注共建社区。


              参考文献

              FUSE documentation
              https://www.cs.hmc.edu/~geoff/classes/hmc.cs135.201001/homework/fuse/fuse_doc.html#function-purposes
              FUSE example
              https://github.com/libfuse/libfuse/tree/master/example
              pr: fuse: add max_pages to init_out
              https://github.com/torvalds/linux/commit/5da784cce4308

              ------ END. ------

              🔥 火爆报名中:
              2023 GLCC | Curve 邀你开源编程夏令营,赢万元奖金
              2023 开源之夏 | Curve 邀你与中国存储软件共成长,赢万元奖金
              Curve丨Google 编程之夏 2023 招募中
              🔥 推荐用户案例:
              Curve 文件存储在 Elasticsearch 冷热数据存储中的应用实践
              扬州万方:基于申威平台的 Curve 块存储在高性能和超融合场景下的实践
              创云融达:基于 Curve 块存储的超融合场景实践 
              🔥 推荐硬核技术解析:
              Curve 混闪之 bcache 与 open-cas 对比 
              Curve 基于 SPDK target 的 iSCSI 性能优化实践

              AI 场景中的 IO 行为浅析



              关于 Curve 

              Curve 是一款高性能、易运维、云原生的开源分布式存储系统。可应用于主流的云原生基础设施平台:对接 OpenStack 平台为云主机提供高性能块存储服务;对接 Kubernetes 为其提供 RWO、RWX 等类型的持久化存储卷;对接 PolarFS 作为云原生数据库的高性能存储底座,完美支持云原生数据库的存算分离架构。

              Curve 亦可作为云存储中间件使用 S3 兼容的对象存储作为数据存储引擎,为公有云用户提供高性价比的共享文件存储。

              • GitHub:https://github.com/opencurve/curve
              • 官网https://opencurve.io/
              • 用户论坛:https://ask.opencurve.io/
                微信群:搜索群助手微信号 OpenCurve_bot



              文章转载自OpenCurve,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论