ToplingDB SidePlugin 配置系统

ToplingDB 配置系统

当年在  TerarkDB 中,我为了实现无缝集成  TerarkZipTable,避免用户修改代码,使用了一种非常 Hack 的方案:在  DB::Open 中拦截配置,如果发现了相关的环境变量,就启用  TerarkZipTable。 这样就允许用户不用修改代码,只需要定义环境变量就能使用  TerarkZipTable

这种配置方式实现了 TerarkDB 当时的预期目标,但只是一个简陋的补丁!所以,在 ToplingDB 的设计中,DB 的插件化配置是一个中心问题,经过深思熟虑,就得出了 SidePlugin 这样一个完备的、系统化的解决方案。

更多关于 ToplingDB 配置系统 的设计动机,请参考  Motivation To Solution

1.  概要

ToplingDB 配置系统采用 json/yaml 格式定义配置项,将 ToplingDB/RocksDB 的所有元对象均纳入此配置系统。总体而言,ToplingDB 配置系统实现了以下目标:

  1. 1.ToplingDB/RocksDB 的所有配置需求
  2. 2.无缝插件化:用户代码无需修改,即可插入第三方模块(例如 ToplingZipTable)
  3. 3.观测:通过  Web Service 展示引擎内部状态(相关文档)
  4. 4.监控:通过  Web Service 将引擎指标导出到 Prometheus,进而使用 grafana 可视化
  5. 5.简化多语言 Binding (只需要 bind class SidePluginRepo 即可)

2.  详细介绍

ToplingDB/RocksDB 的根配置对象是 DBOptions 和 ColumnFamilyOptions,额外的 Options 对象是 DBOptions 和 ColumnFamilyOptions(简称 CFOptions) 的结合体(从后两者继承而来)。

DBOptions 和 CFOptions 中包含二级配置对象,有些二级对象还进一步包含三级配置对象。所有这些对象,都定义为 json 中以其基类名命名的一级 json 对象的子对象,另外,json 中还有另外几个特殊的一级 json 对象(http,setenv,databases,open)。可以在 json 对象中引用其它 json 对象,这些引用将转化成 C++ 对象间的引用关系。

2.1.  json 配置示例

{
    "http": {
      "document_root": "/path/to/dbname",
      "listening_ports": "8081"
    },
    "setenv": {
      "DictZipBlobStore_zipThreads": 8,
      "StrSimpleEnvNameNotOverwrite": "StringValue",
      "IntSimpleEnvNameNotOverwrite": 16384,
      "OverwriteThisEnv": { "overwrite": true,
        "value": "overwrite is default to false, can be manually set to true"
      }
    },
    "permissions": { "web_compact": true },
    "Cache": {
      "lru_cache": {
        "class": "LRUCache",
        "params": {
          "capacity": "4G", "num_shard_bits": -1, "high_pri_pool_ratio": 0.5,
          "strict_capacity_limit": false, "use_adaptive_mutex": false,
          "metadata_charge_policy": "kFullChargeCacheMetadata"
        }
      }
    },
    "WriteBufferManager" : {
      "wbm": {
        "class": "Default",
        "params": {
          "//comment": "share mem budget with cache object ${lru_cache}",
          "buffer_size": "512M", "cache": "${lru_cache}"
        }
      }
    },
    "Statistics": { "stat": "default" },
    "TableFactory": {
      "bb": {
        "class": "BlockBasedTable",
        "params": { "block_cache": "${lru_cache}" }
      },
      "fast": {
        "class": "ToplingFastTable",
        "params": { "indexType": "MainPatricia" }
      },
      "zip": {
        "class": "ToplingZipTable",
        "params": {
          "localTempDir": "/dev/shm/tmp",
          "sampleRatio": 0.01, "entropyAlgo": "kNoEntropy"
        }
      },
      "dispatch" : {
        "class": "DispatcherTable",
        "params": {
          "default": "bb",
          "readers": { "ToplingFastTable": "fast", "ToplingZipTable": "zip" },
          "level_writers": ["fast", "fast", "bb", "zip", "zip", "zip", "zip"]
        }
      }
    },
    "CFOptions": {
      "default": {
         "max_write_buffer_number": 4, "write_buffer_size": "128M",
         "target_file_size_base": "16M", "target_file_size_multiplier": 2,
         "table_factory": "dispatch", "ttl": 0
      }
    },
    "databases": {
      "db1": {
        "method": "DB::Open",
        "params": {
          "options": {
            "write_buffer_manager": "${wbm}",
            "create_if_missing": true, "table_factory": "dispatch"
          }
        }
      },
      "db_mcf": {
        "method": "DB::Open",
        "params": {
          "db_options": {
            "create_if_missing": true,
            "create_missing_column_families": true,
            "write_buffer_manager": "${wbm}",
            "allow_mmap_reads": true
          },
          "column_families": {
            "default": "$default",
            "custom_cf" : {
              "max_write_buffer_number": 4,
              "target_file_size_base": "16M",
              "target_file_size_multiplier": 2,
              "table_factory": "dispatch", "ttl": 0
            }
          },
          "path": "'dbname' passed to Open. If not defined, use 'db_mcf' here"
        }
      }
    },
    "open": "db_mcf"
  }

2.2.  特殊对象

2.2.1.  http

在这个示例中,第一个 json 子对象是:

"http": {
     "document_root": "/", "listening_ports": "8081"
 }

这个 http 对象定义了用作 Web 展示的 Http Web Server 配置。完整的 http 参数请参考: CivetWeb UserManual

2.2.2  setenv

"setenv": {
     "DictZipBlobStore_zipThreads" : 8
 }

setenv 的每个子对象定义一个环境变量。

2.2.3.  permissions

permissions 的每个子对象定义一个权限。

2.2.4.  databases

可以在 databases 之下定义多个 database 对象,database 对象分为两大类: 1. 只包含默认 ColumnFamily 的 DB 2. 包含多个 ColumnFamily 的 DB ( DB_MultiCF

这两类 database 通过其是否包含子对象 column_families来区分。哪怕一个 database 实际上只有一个 ColumnFamily,但它将该 ColumnFamily 定义在子对象  column_families 中,那它也是  DB_MultiCF

database 对象通过 method 指定的函数打开,C++ 代码中 method 是重载的,在 json 中 method 也是重载的,相同的 method,对 DB 和  DB_MultiCF 分别重载。

2.2.5.  open

虽然我们可以在 json 中定义多个 database,但很多时候,我们只会打开其中的一个 database,当使用无 database 名字的 OpenDB api 时,这个 open 对象用来指定打开哪个 database。当用户使用了带 db 名字的 OpenDB api 时,这个 open 对象就被忽略。

2.3.  一般对象

json 的一级对象中,除以上 4 种特殊对象,其它都是一般对象,每个一级的一般对象的名字是 ToplingDB/RocksDB 中此类对象的基类的类名。例如示例中的 "Cache", "Statistics", "TableFactory",这些一级对象本身相当于一个容器,其中每个子对象定义了真正的 C++ 对象。这样的每个“容器”相当于一个 namespace,不同 namespace 下可以有同名的对象。

json 对象对应的 C++ 对象包含类名和参数,分别通过 "class" 和 "params" 来表达。细心的用户可以发现,对象名为 "stat" 的 json 是字符串 "default",这是为了简化,对于无参的 class,可以直接使用其类名的字符串来定义(此处"default" 是 stat 的注册类名,对应的 C++ 类是 StatisticsImpl),当然,这种对象也可以用一个包含 "class" 和 "params" 的完整正规的 json 对象来定义。

DBOptions 和 CFOptions 是比较特殊的一般对象,因为其 "class" 是确定的,所以免去了 "class" 和 "params",直接将 "params" 中的成员提升到外层。

2.4.  对象引用

在 C++ 对象中,一个对象引用另一个对象通过指针来实现,在 json 中,通过对象名来实现,对象引用的正式的完全的写法是 "\${varname}",简化写法可以是 "\$varname" 或 "varname",其中,"varname"可能会导致歧义,因为一个 json 字符串也可能表达的是  "class_name"。我们的处理方式是:先看该字符串是否是一个已定义的对象,如果是,就按照"varname"处理,否则按照  "class_name" 处理。

2.4.1.  内嵌对象

除了定义命名的对象,然后按名字引用对象,我们也可以定义内嵌对象,例如示例中的:

"custom_cf" : {
      "max_write_buffer_number": 4,
      "target_file_size_base": "16M",
      "target_file_size_multiplier": 2,
      "table_factory": "dispatch", "ttl": 0
  }

"custom_cf" 可以定义为一个 CFOptions 对的引用,但是在这里,把它定义为一个内嵌对象更加方便简洁。

2.4.2.  CFOptions::ttl

CFOptions 中没有 ttl 成员,但我们在 json 中为它定义了 ttl,这是因为,database 的 "method" 除了可以指定为 "DB::Open",还可以指定为其它很多函数:

"DB::OpenForReadOnly" // 等效于在 params 中定义 read_only: true
  "DBWithTTL::Open" // 需要 CFOptions::ttl  
  "TransactionDB::Open"
  "OptimisticTransactionDB::Open"
  "BlobDB::Open"

用户还可以扩展定义自己的 Open 例如:MyCustomDB::Open。

2.5.  DispatcherTable

"dispatch" : {
    "class": "DispatcherTable",
    "params": {
      "default": "bb",
      "readers": {"ToplingFastTable": "fast", "ToplingZipTable": "zip"},
      "level_writers": ["fast", "fast", "bb", "zip", "zip", "zip", "zip"]
    }
  }

顾名思义,DispatcherTable 用来进行实际的 Table(SST) 分发及调度,对用户来讲,最关键的是  level_writers:在相应的 level 上使用相应的 Table。

default 用来定义当 level < 0 时(level 是 TableBuilderOptions 的成员),或者  level_writer 创建 builder 失败时,作为 fallback。

readers 用来定义  class_name 到 varname 的映射,因为在内部实现中,加载 Table 是通过  DispatcherTable::NewTableReader 实现的,作为 dispather,自然需要知道加载的 Table 具体是哪种 Table,这是通过 TableMagicNumber 来区分的,是编译期静态确定的,但是 TableFactory 是运行时创建的,并且每个具体的 TableFactory class 可以有多个(params 不同的) object,所以,我们需要在这里指定相应的 TableFactory class 使用哪个 TableFactory object 来加载。

在这个 DispatcherTable 定义中,L0\~L2 使用 fast,L3\~L6 使用 zip。

3.  Yaml 文件

熟悉 Kubernetes 的用户,可能更喜欢 Yaml,作为配置文件,Yaml 的可读性更好,ToplingDB 配置系统也支持 Yaml。

4.  几个实际的配置文件

Json Yaml 说明
etcd_dcompaction.json yaml 带分布式 Compact
lcompact_sample.json yaml 不带分布式 Compact
todis-community.json yaml todis 社区版(无性能组件)
todis-enterprise.json yaml todis 企业版(有性能组件)

知乎表格中无法插入链接,链接见下:

etcd_dcompaction.json  yaml 带分布式 Compact

lcompact_sample.json  yaml 不带分布式 Compact

todis-community.json  yaml todis 社区版(无性能组件)

todis-enterprise.json  yaml todis 企业版(有性能组件)


请使用浏览器的分享功能分享到微信等