从0开始使用ToplingDB

从 0 开始使用 ToplingDB

部署安装 ToplingDB

  1. 服务器环境

    操作系统: CentOS Linux release 8.4.2105

    g++版本: g++ (GCC) 8.4.1 20200928 (Red Hat 8.4.1-1)

  2. 安装相关依赖

    ToplingDB 基于 RocksDB 构建,我们需要用到压缩库 snappy 和命令行参数解析工具 gflags 。除此之外,在编译的过程中,还需要用到 libaio 的开发包。

    • 安装 snappy :

      1
      sudo yum install snappy snappy-devel
    • 安装 gflags :

      • 对于 CentOS 8:

        1
        sudo dnf --enablerepo=powertools install gflags-devel
      • 对于 CentOS 7(需要 EPEL ):

        1
        sudo yum install gflags-devel
    • 安装 libaio-devel :

      1
      sudo yum install libaio-devel
  3. 安装 ToplingDB

    • 获取项目源代码:

      1
      2
      cd ~
      git clone https://github.com/topling/toplingdb.git
    • 更新依赖的子项目:

      1
      2
      cd toplingdb
      git submodule update --init --recursive
    • 编译安装动态库:

      1
      2
      make shared_lib
      sudo make install
    • 设置环境变量:

      除了 librocksdb.so 之外,我们还会用到 topling-zip 编译生成的 libterark-zbs-r.so 等动态库。在刚才的 make 过程中, topling-zip 已被克隆到 toplingdb/sideplugin 目录下,它编译得到的动态库位于 topling-zip/build/Linux-x86_64-g++-8.4-bmi2-1/lib_shared

      打开文件 ~/.bashrc ,在文件的末尾增加下列两行:

      1
      2
      export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
      export LD_LIBRARY_PATH=~/toplingdb/sideplugin/topling-zip/build/Linux-x86_64-g++-8.4-bmi2-1/lib_shared:$LD_LIBRARY_PATH

      保存后,执行以下命令,更新我们的设置:

      1
      source ~/.bashrc

      需要注意的是, Linux-x86_64-g++-8.4-bmi2-1 这一目录名称是根据编译环境而自动命名的。若您的编译环境与本文环境不同,您需要自行查看具体的目录,并调整之前设置的环境变量路径。

通过配置文件打开数据库

ToplingDB 是一个嵌入式数据库,数据库的库文件直接链接在应用程序中,应用程序通过调用 API 来进行数据库的读写操作。

在本文的所有示例中,我们将数据库放置在路径 /home/topling/db/ 下,也就是用户主目录下的 db 文件夹中。所有编写的代码和配置文件也放在用户主目录 /home/topling/ 下。

  1. 创建配置文件和数据库目录

    执行下列命令建立存放数据库的文件夹。

    1
    2
    3
    cd ~
    mkdir -p db
    mkdir -p db/db_mcf

    在同一目录下创建配置文件 toplingconf.json ,然后找到我们的示例配置文件,将它里面的配置信息复制进来。

    接下来,修改配置信息中的数据库路径信息 path ,它位于最末尾的 db_mcf 字段中。将它修改为你自己的用户主目录下的db文件夹下的 db_mcf

    1
    "path": "/home/topling/db/db_mcf"

    更多关于配置文件的信息,请参阅配置系统介绍

  2. 创建操作数据库的 .cc/.cpp/.cxx 文件

    在用户主空间下,创建包含 main 函数的文件 sample.cpp ,加载我们会用到的头文件 topling/side_plugin_repo.h ,以及标准输入输出流的头文件 iostream

    1
    2
    #include "topling/side_plugin_factory.h"
    #include <iostream>

    在主函数中,创建一个 rocksdb::SidePluginRepo 类的实例 repo 。调用它的成员函数 ImportAutoFile ,从我们刚才写好的配置文件中加载配置信息。

    1
    2
    rocksdb::SidePluginRepo repo;    // Repo represents of ConfigRepository
    repo.ImportAutoFile("/home/topling/toplingconf.json");

    在示例的配置信息中,打开的数据库是 db_mcf ,这是一个包含多个 ColumnFamily 的 DB ,对应类型 rocksdb::DB_MultiCF 。创建一个该类型的指针 dbm 来接收打开的数据库,并将返回的 rocksdb::Status 中的信息打印出来。如果返回的是 OK ,则表示打开成功。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include "topling/side_plugin_factory.h"
    #include <iostream>

    int main()
    {
    rocksdb::SidePluginRepo repo;
    repo.ImportAutoFile("/home/topling/toplingconf.json");

    rocksdb::DB_MultiCF *dbm;
    auto status = repo.OpenDB(&dbm);
    std::cout << status.ToString() << std::endl;

    return 0;
    }
  3. 编译

    使用以下指令进行编译,输出可执行文件 sample.out

    1
    g++ sample.cpp -I ~/toplingdb/sideplugin/rockside/src -I ~/toplingdb -I ~/toplingdb/sideplugin/topling-zip/src -I ~/toplingdb/sideplugin/topling-zip/boost-include -l:librocksdb.so -DSIDE_PLUGIN_WITH_YAML=1  -DROCKSDB_NO_DYNAMIC_EXTENSION=1 -o sample.out

    使用命令 ./sample.out 执行生成的二进制文件。不出意外,我们将看到终端打印出 OK ,这表示我们正确地打开了数据库。

  4. 对数据库的简单读写操作

    在打开数据库后, dbm 中有两个重要的成员变量:指向数据库实例的指针 db 和储存所有 ColumnFamilyHandle 的 vector 容器 cf_handles

    1
    2
    auto db = dbm -> db;
    auto handles = dbm -> cf_handles;

    通过它们就可以像操作 RocksDB 一般,对 ToplingDB 进行读写了。如果我们在此基础上增加对输入命令的解析,就成了一个简单的服务式的 KV数据库程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // write
    db -> Put(rocksdb::WriteOptions(), handles[0], rocksdb::Slice("test"), rocksdb::Slice("default_cf");
    db -> Put(rocksdb::WriteOptions(), handles[1], rocksdb::Slice("test"), rocksdb::Slice("custom_cf");

    //read
    std::string value1 , value2;
    db -> Get(rocksdb::ReadOptions(), handles[0], rocksdb::Slice("test"), &value1);
    db -> Get(rocksdb::ReadOptions(), handles[1], rocksdb::Slice("test"), &value2);
    std::cout << value1 << std::endl;
    std::cout << value2 << std::endl;

    //delete
    status = db -> Delete(rocksdb::WriteOptions(), handles[0], rocksdb::Slice("test"));
    std::cout << status.ToString() << std::endl;
    status = db -> Delete(rocksdb::WriteOptions(), handles[0], rocksdb::Slice("not exist"));
    std::cout << status.ToString() << std::endl;

更换 SST Table

ToplingDB 支持旁路插件化,只通过更改配置文件就可以更换 SST 文件的 TableFactory ,无需修改代码。

  • 使用 RocksDB 内置的 SST

    修改配置文件中 TableFactory 的部分,增加不同 Table 类型的配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    "TableFactory": {
    "block_based": {
    "class": "BlockBasedTable",
    "params": {

    }
    },
    "cuckoo": {
    "class": "CuckooTable",
    "params": {

    }
    },
    "plain": {
    "class": "PlainTable",
    "params": {

    }
    }
    },

    然后在 database 的部分中,使用我们新设置的 table :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    "SliceTransform": {
    "default": {
    "class" : "FixedPrefixTransform",
    "params" :{
    "prefix_len" : 10
    }
    }
    },
    "database": {
    ...

    "column_families": {
    "default": "$default",
    "custom_cf" : {
    "max_write_buffer_number": 4,
    "target_file_size_base": "16M",
    "target_file_size_multiplier": 2,
    "table_factory": "block_based",
    "ttl": 0
    },
    "cuckoo_cf" : {
    "table_factory": "cuckoo"
    },
    "plain_cf" : {
    "table_factory": "plain",
    "prefix_extractor" : "$default"
    }
    },
    }

    直接运行我们之前的程序,现在打开的数据库中, cuckoo_cfplain_cf 这两个 ColumnFamily 就已经使用了新的 Table 而不是默认的 BlockBasedTable 。

    如果您在这一步遇到了问题,也可以参考 2-1-toplingconf.json

  • 使用第三方 SST 文件

    只需要通过 ROCKSDB_FACTORY_REG 宏注册第三方的 Factory ,就可以像使用 RocksDB 内置类型一样使用第三方 SST 文件。

    为了进行一个简单的示范,我们稍微包装一下 BlockBasedTable ,拿它当作一个第三方 SST 文件。

    1. 创建 mysst.h

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // mysst.h

      #define ROCKSDB_PLATFORM_POSIX
      #include "table/block_based/block_based_table_factory.h"
      namespace rocksdb
      {
      struct MyBlockBasedTableOptions : public BlockBasedTableOptions {};

      class MyBlockBasedTableFactory : public BlockBasedTableFactory
      {
      public:
      explicit MyBlockBasedTableFactory(
      const MyBlockBasedTableOptions& table_options = MyBlockBasedTableOptions());
      const char* Name() const;
      ~MyBlockBasedTableFactory() {};
      };

      }
    2. 创建 mysst.cpp

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      // mysst.cpp

      #include "mysst.h"
      #include <iostream>

      namespace rocksdb
      {

      MyBlockBasedTableFactory::MyBlockBasedTableFactory(const MyBlockBasedTableOptions& _table_options)
      : BlockBasedTableFactory(_table_options)
      {
      std::cout << "Using MyBlockBasedTableFactory" << std::endl;
      }

      const char* MyBlockBasedTableFactory::Name() const
      {
      return "MyBlockBasedTableFactory";
      };

      }

      可以看到 MyBlockBasedTable 只是继承了 BlockBasedTable 而已,没有其它的改动。只不过当我们使用 MyBlockBasedTable 时,执行它的构造函数会打印出 “Using MyBlockBasedTableFactory” 。

    3. 注册 MyBlockBasedTable

      mysst.cpp 文件中,增加以下部分:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      #include "topling/side_plugin_factory.h"
      namespace rocksdb
      {

      std::shared_ptr<TableFactory> ThirdSSTExample(const json& js , const SidePluginRepo& repo)
      {
      return std::make_shared<MyBlockBasedTableFactory>(MyBlockBasedTableOptions());
      }
      ROCKSDB_FACTORY_REG("MyBlockBased", ThirdSSTExample);
      }

      修改完成后的代码可以参考 2-2-3-mysst.cpp

      这里为了方便起见,我们总是使用默认的配置项来构造 MyBlockBasedTable 。在实际使用中,您应该通过 js 中保存的 json 信息来构造您使用的 TableFactory ,它类似这样:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      std::shared_ptr<TableFactory> ThirdSSTExample(const json& js , const SidePluginRepo& repo)
      {
      ThirdTableOptions table_options;

      // some code for modifying table_options by json
      ...
      ...

      return std::make_shared<ThirdTableFactory>(table_options);
      }
      ROCKSDB_FACTORY_REG("MyBlockBased", ThirdSSTExample);
    4. 编译生成 libmysst.so

      执行以下指令进行编译,生成自定义插件 MyBlockBasedTable 的动态库 libmysst.so :

      1
      g++ mysst.cpp -I ~/toplingdb -I ~/toplingdb/sideplugin/rockside/src -I ~/toplingdb/sideplugin/topling-zip/src -I ~/toplingdb/sideplugin/topling-zip/boost-include -l:librocksdb.so -fPIC -shared -o libmysst.so
    5. 动态加载 libmysst.so :

      设置环境变量 LD_PRELOAD 后,直接运行我们之前的可执行程序 sample.out :

      1
      LD_PRELOAD=./libmysst.so ./sample.out

      此时 MyBlockBasedTable 已经注册进 ToplingDB ,现在就可以像之前使用 RocksDB 内置的 PlainTable 、 CuckooTable 一般,直接在配置项中启用 MyBlockBasedTable 了。

      在配置文件中进行如下修改,将内置类型 BlockBasedTable 改为第 3 步中,我们用 ROCKSDB_FACTORY_REG 宏注册的名称 “MyBlockBased” 。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      "TableFactory": {
      "block_based": {
      "class": "MyBlockBased",
      "params": {

      }
      },

      ...
      }

      再次运行 sample.out (不要忘记设置 LD_PRELOAD !),就能看到 MyBlockBasedTable 在构造函数中打印的提示信息了。

使用 AnyPlugin 进行 HTML 展示

为了方便,本示例在 sample.cpp 的基础上直接进行修改,没有单独将 HTML 展示插件编译为动态库。

  1. 注册 AnyPlugin 插件

    rocksdb 命名空间内,定义 AnyPlugin 的派生类 HtmlShowExample ,并修改它的 ToString 函数和 Name 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    namespace rocksdb {
    class HtmlShowExample : public AnyPlugin
    {
    public:
    void Update(const json&, const SidePluginRepo&) {}
    std::string ToString(const json& dump_options, const SidePluginRepo&) const
    {
    return "This is an example of HTML show.";
    }
    const char* Name() const
    {
    return "HtmlShowExample";
    }
    };
    }

    ToString 函数的返回值是 std::string 类型,其返回的 string 字符串,会被无差别地打印在浏览器中。若返回值是一个序列化的 json 对象, AnyPlugin 还能够以表格的形式展示数据。

    定义派生类 HtmlShowExample 之后,仍然在 rocksdb 命名空间中,使用下列的宏将其注册。

    1
    2
    ROCKSDB_REG_DEFAULT_CONS(HtmlShowExample, AnyPlugin);
    ROCKSDB_REG_AnyPluginManip("HtmlShowExample");
  2. 开启 http 服务

    在加载配置文件后,调用 repo 的成员函数 StartHttpServer 开启 http 服务。与打开 DB 相似,我们也可以打印出返回的 rocksdb::Status 的相关信息作为参考。

    1
    2
    auto http_status = repo.StartHttpServer();
    std::cout << http_status.ToString() << std::endl;

    修改后的源程序为 3-2-sample.cpp

  3. 修改配置文件

    在配置文件的最外层中,增加我们的展示插件信息。

    1
    2
    3
    4
    5
    6
    7
    {
    "AnyPlugin": {
    "html-show-example": "HtmlShowExample"
    },

    ...
    }
  4. 编译并运行项目

    使用我们之前的编译指令编译修改后的 sample.cpp ,并执行程序。

    我们在示例配置文件中设置的监听端口为 8081 ,访问 127.0.0.1:8081/AnyPlugin/html-show-example ,即可看到展示信息。

    如果您不是在本地上执行程序,将 127.0.0.1 更改为您机器的访问ip。如果执行程序打印的信息均为OK,但无法打开页面,请检查防火墙设置。

  5. 其他信息展示

    ToplingDB 内部集成了一个 WebService 用于对外展示内部信息,例如目前配置的参数选项,LSM树的状态,或者是分布式 compact 的执行情况等等。另外,在 Statistic 下展示的监控指标,还可以导入到 Prometheus + Grafana 中进行监控。

    直方图全域展示

    若您还使用了第三方插件,在实现并注册对应的 PluginManipFunc 模板类后,即可在对应的 web 页面下看到 ToString 成员函数返回的序列化信息。