Skip to content

Ingest Predefine Object

rockeet edited this page Apr 18, 2023 · 3 revisions

注入预定义对象

我们在 CompactionFilterFactory As SidePlugin 中描述了如何向插件系统注册自定义的 CompactionFilterFactory,然而这在很多情况下略有繁琐。

简化的方法

在 旁路插件化配置系统 设计伊始,我们就考虑了这个问题,并设计了相应的接口:

class SidePluginRepo {
public:
  // more ...
  void Put(const std::string& name, json spec, const std::shared_ptr<CompactionFilterFactory>&);
  void Put(const std::string& name, json spec, const std::shared_ptr<RateLimiter>&);
  void Put(const std::string& name, json spec, const std::shared_ptr<EventListener>&);
  void Put(const std::string& name, const char* spec, const std::shared_ptr<CompactionFilterFactory>&);
  void Put(const std::string& name, const char* spec, const std::shared_ptr<RateLimiter>&);
  void Put(const std::string& name, const char* spec, const std::shared_ptr<EventListener>&);
  // more ...
};

使用 Put,用户可以不用将自己的 CompactionFilterFactory/RateLimiter 等注册到插件体系,而是直接把创建出来的 CompactionFilterFactory/RateLimiter 等对象注入到配置库中,在 MyTopling 中,就大量使用了这个 Put,例如 ha_rocksdb.cc 中:

    auto listener = std::make_shared<Rdb_event_listener>(&ddl_manager);
    g_repo.Put("rdb_listener", json{
      {"class", "Rdb_event_listener"},
      {"params", {"empty", "param"}},
    }, listener);
    db_opt->listeners.push_back(listener);
//---------------------
    rocksdb_db_options->rate_limiter = rocksdb_rate_limiter;
    g_repo.Put("rate_limiter", json{
      {"class", "GenericRateLimiter"},
      {"params", {"rate_bytes_per_sec", rocksdb_rate_limiter_bytes_per_sec}}
    }, rocksdb_db_options->rate_limiter);

Put 中的 json spec 参数是可选的,其用处仅仅是 Web 展示时更有可读性。必须注意的是,这个注入的对象最终也是手工赋值给其它配置对象,并最终通过 db_opt 或 cf_opt 对它的直接或间接引用生效,例如上面代码中的:

    // 这两处都是通过 db_opt 直接引用,间接引用的例子如被 BlockBasedTable 引用的 BlockCache
    db_opt->listeners.push_back(listener); // db_opt 即 rocksdb_db_options
    rocksdb_db_options->rate_limiter = rocksdb_rate_limiter;

MyTopling 的 Rdb_compact_filter_factory 并未使用这种方式注入配置库,并非因为它不能使用这种方式,而是因为 Rdb_compact_filter_factory 需要支持序列化,要支持序列化,就必须注册,既然注册了,那就用标准的配置方式。

使用字符串表达 json

从前面的 Put 重载中可以看到,除了传递 json 对象之外,还可以传递 json 字符串,在 kvrocks ToplingDB 中,我们就使用了这个重载:

repo_.Put("metadata",
  R"({"class": "MetadataFilterFactory", "params": {"storage": "this"}})",
  shared_ptr<CompactionFilterFactory>(make_shared<MetadataFilterFactory>(this)));
repo_.Put("subkey",
  R"({"class": "SubKeyFilterFactory", "params": {"storage": "this"}})",
  shared_ptr<CompactionFilterFactory>(make_shared<SubKeyFilterFactory>(this)));
repo_.Put("listener",
  R"({"class": "EventListener", "params": {"storage": "this"}})",
  shared_ptr<rocksdb::EventListener>(make_shared<EventListener>(this)));

使用该重载,有两方面好处:

  1. 字符串表达的 json 是标准 json 语法,比 C++ initializer list 表达的 json 可读性更好
    • 坏处是 json 语法检查要延迟到运行时,必要时需要 catch exception
  2. 可以避免 #include json 头文件,只需要 #include <topling/side_plugin_repo.h>
    • 此时 class json 只是前置声明,并未定义,编译速度可以加快那么一丢丢

适用场景

  1. 代码迁移:从 RocksDB 迁移到 ToplingDB,这种方式代码改动更小。
  2. 对象构造太复杂:使用这种方式,我们就把对象的构造代码完全当成黑盒来使用。