利用RocksDB 的plugin机制对接ServerSAN

本文介绍了如何在云原生环境中改造RocksDB,使其直接运行在SANDriver之上,减少性能损耗。通过RocksDB的Plugin机制,实现了对接ServerSAN存储的AOFPlugin,简化了IO栈,并详细阐述了Plugin的注册和实现过程。改造后的架构无需内核文件系统转接,便于容器化和函数计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目模式与传统模式的区别

通常rocksdb是运行在一个本地硬盘文件系统上,在云时代传通硬盘通常被云硬盘,也就是各种EBS代替。此时传统的 rocksdb模式并非不能运行,但是效率却非最优的。这种场景的IO栈如下:

现在的云原生技术追求最少的性能损耗,特别是当应用运行在容器内部时应尽量不要依赖内核服务,这对性能、安全性、弹性能力都有很大影响。因此直接将RocksDB直接运行在SAN Driver之上是一个很有价值的方案。

我们希望经过改造后的架构在client侧只有一层RocksDB应用层,而没有不同kernel的技术栈。如下图所示,虽然细分仍包含了plugin/API/SNA client模块,但这些模块都包含在RocksDB这个进程内,而且这些模块并非新增,只是原来系统的模块替代。

经过这样的架构改造后无论是进行容器化,抑或是适配函数计算都容易了很多,不再需要内核文件系统转接。

简单解释下上图中涉及的几个名词:

SAN Cluster:是存储集群,这里特指ServerSAN集群。通常ServerSAN会提供私有的SAN Client,比如ceph会提供librbd, NeonSAN会提供libqbd,PureFlash会提供libpfbd,原理都是类似的。

SAN Client:这里指ServerSAN私有的用户态API库。如上面说的libpfbd等。这类接口相比经由内核态的iSCSI, FC访问存储的路径更短、效率更高。

AOF API是在SAN Client接口上将volume以Append Only File的形式提供出来,实现一个从块设备到文件的语义转换。虽然这个转换是一个质变,但因为AOF语义非常简单,所以这一层仍然是很薄的。本文重点不在这一层,我会专文再写。

AOF plugin: 一个RocksDB plugin,将AOF对接到RocksDB上。这是本文今天介绍的部分。

二、RocksDB plugin机制

说实话不知道plugin机制是从哪个版本开始有的,似乎是2021年初才出现的。在有Plugin机制之前,如果想要扩展rocksdb使用一个新的文件系统,典型的比如HDFS,需要在rocksdb的代码树里添加代码,代码耦合比较紧密,不利于各自独立发展。有了plugin机制之后,扩展部分代码可以单独成库,和rocksdb自身代码完全分开。官方提供的一个例子代码在这里:GitHub - ajkr/dedupfs

三、实现plugin

1)注册

plugin是独立于rocksdb代码树之外的,需要通过注册机制告诉rockdb:我这里有一个plugin,可以处理xxx类型的文件系统。注册的代码如下:

extern "C" FactoryFunc<FileSystem> pfaof_reg;
FactoryFunc<FileSystem> pfaof_reg =
    ObjectLibrary::Default()->Register<FileSystem>(
        "pfaof", [](const std::string& /* uri */,
                    std::unique_ptr<FileSystem>* f, std::string* /* errmsg */) {
          *f = std::unique_ptr<FileSystem> (new PfAofFileSystem());
          return f->get();
        });

可以看到,注册过程将一个文件系统类型"pfaof"和一个工厂函数建立了关联。当应用要求pfaof类型的文件系统时,这个工厂函数返回一个PfAofFileSystem对象。

2) 实现

PfAofFileSystem就是plugin实现的主体部分。这个类需要继承自定义在rocksdb/include/rocksdb/file_system.h中的类FileSystem.  这个类里面定义了一些纯虚函数,和一些带有缺省实现的虚函数。

毫无疑问纯虚函数是子类必须实现的,否则我们的子类将无法工作。比较麻烦的是那些有缺省实现的虚函数,这些函数通常返回IOStatus::NotSupported(),直到运行时才报告错误。

PfAofFileSystem的完整实现大家可以  Gitee-pfaof实现   。这里贴出几个关键函数:

class PfAofFileSystem : public FileSystem {
public:
    virtual IOStatus NewSequentialFile(
        const std::string& fname, const FileOptions& file_opts,
        std::unique_ptr<FSSequentialFile>* result, IODebugContext* dbg) {
      (void)file_opts;
      (void)dbg;
      PfAof* aof = pf_open_aof(fname.c_str(), NULL, O_CREAT | O_RDWR,
                               "/etc/pureflash/pf.conf", S5_LIB_VER);
      if (aof == NULL) return IOStatus::PathNotFound();
      *result = std::unique_ptr<FSSequentialFile> (new PfAofSeqFile(aof, fname));
      return IOStatus::OK();
    }
    virtual IOStatus NewRandomAccessFile(
        const std::string& fname, const FileOptions& file_opts,
        std::unique_ptr<FSRandomAccessFile>* result,
        IODebugContext* dbg)  {
      (void)file_opts;
      (void)dbg;
      PfAof* aof = pf_open_aof(fname.c_str(), NULL, O_CREAT | O_RDWR,
                               "/etc/pureflash/pf.conf", S5_LIB_VER);
      if (aof == NULL) return IOStatus::PathNotFound();
      *result =
          std::unique_ptr<FSRandomAccessFile> (new PfAofRandomFile(aof, fname));
      return IOStatus::OK();
    }
    virtual IOStatus NewWritableFile(const std::string& fname,
                                     const FileOptions& file_opts,
                                     std::unique_ptr<FSWritableFile>* result,
                                     IODebugContext* dbg) {
      (void)file_opts;
      (void)dbg;
      PfAof* aof = pf_open_aof(fname.c_str(), NULL, O_CREAT | O_RDWR,
                               "/etc/pureflash/pf.conf", S5_LIB_VER);
      if (aof == NULL) return IOStatus::PathNotFound();
      *result =
          std::unique_ptr<FSWritableFile> (new PfAofWriteableFile(aof, fname));
      return IOStatus::OK();
    }
};

rocksdb对FileSystem的接口要求已经是高度抽象化的了,没有要求一定是标准C里定义的FILE结构体,也没有要求是Posix的fd。必须实现的函数中的3个就是NewSequentialFile, NewRandomAccessFile, NewWritableFile。 这三个函数的名字已经非常清晰的表达了他们的作用,在头文件file_system.h中的注释也解释的非常清楚,这里就不复制粘贴了。简单讲,这三个函数分别用于创建三种不同类型的文件;不同类型的文件需要能提供不同读写能力,就是:随机读(RandomAccessFile), 顺序读(SequentialFile), 顺序写(WritableFile)。

从父类FileSystem对这三个函数的定义容易看出,这三个函数分别要返回三个不同类型的xxxFile。显然,这里又要自己实现三个File的子类。而PureFlash的AOF刚好提供了这三种能力,因此在实现上这三种文件全都由PfAof承担。

这里值得一提的是线程安全问题,RandomAccessFile要求plugin实现方保证线程安全,其他两个文件类型都由调用方保证线程安全。

四、编译

如何将application, rocksdb, plugin三者编译到一起呢,我们以rocksdb自带的simple_example为例

假定我们有如下的目录结构(省略了rocksdb自身的文件):

~/workspace/rocksdb
              +- examples
              +- plugin
                   + pfaof
                       + pfaof.cc
                       + pfaof.h
                       + pfaof.mk

大家大概也可以看得出,pfaof.mk是编译相关的makefile, 其他两个是源文件。pfaof.mk内容如下:

pfaof_SOURCES = pfaof.cc
pfaof_HEADERS = pfaof.h
pfaof_LDFLAGS = -u pfaof_reg -L$(PF_LIB) -ls5common
pfaof_CXXFLAGS = -I$(PF_INC)

我们要编译的目标是examples目录下的simple_example, 因此编译前首先要进入这个目录

# cd rocksdb/examples
# DEBUG_LEVEL=2 ROCKSDB_PLUGINS="pfaof" PF_INC=~/PureFlash/common/include PF_LIB=~/PureFlash/build_deb/bin make simple_example

这里依赖PureFlash项目,PureFlash可以从https://gitee.com/cocalele/PureFlash得到,这里假定克隆到了~/PureFlash目录。

五、运行

simaple_example编译成功后,运行测试前需要把PureFlash集群准备好。作为测试可以参考 PureFlash存储系统介绍与上手指南  里的方法拉一个docker镜像启动,然后把 simple_example 复制到容器里就可以运行了。

感慨一下rocksdb的设计确实是模块化的非常好,作为单机引擎但并没有固定死在posix文件接口上,为对接HDFS, ZNS SSD , 以及这篇的ServerSAN系统保留了简单易替换接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值