当前位置: 首页 > 工具软件 > kvstore > 使用案例 >

ceph-kvstore-tool

乐正明辉
2023-12-01

ceph-kvstore-tool 使用说明

参考链接:
https://github.com/ceph/ceph/blob/master/doc/man/8/ceph-kvstore-tool.rst
http://www.idcat.cn/ceph-kvstore-tool%E5%B7%A5%E5%85%B7%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D.html
https://blog.csdn.net/Z_Stand/article/details/98967671

1. 简介

ceph-kvstore-tool 是一个 kvstore 操作工具。它允许用户离线操作 leveldb/rocksdb 的数据(就像 OSD 的 omap)。

[root@node-1 ceph-objectstore-tool-test]# ceph-kvstore-tool -h
Usage: ceph-kvstore-tool <leveldb|rocksdb|bluestore-kv> <store path> command [args...]

Commands:
  list [prefix]
  list-crc [prefix]
  dump [prefix]
  exists <prefix> [key]
  get <prefix> <key> [out <file>]
  crc <prefix> <key>
  get-size [<prefix> <key>]
  set <prefix> <key> [ver <N>|in <file>]
  rm <prefix> <key>
  rm-prefix <prefix>
  store-copy <path> [num-keys-per-tx] [leveldb|rocksdb|...] 
  store-crc <path>
  compact
  compact-prefix <prefix>
  compact-range <prefix> <start> <end>
  destructive-repair  (use only as last resort! may corrupt healthy data)
  stats

使用前,关闭 mon 服务 或者 osd 服务

systemctl stop <ceph-mon@node-1>|<ceph-mon@0>

使用完,记得重启服务

systemctl restart <ceph-mon@node-1>|<ceph-mon@0>

2. 示例

查询数据库表项(prefix,前缀)

# cat /var/lib/ceph/mon/ceph-node-1/kv_backend 查询 mon 使用何种 db
ceph-kvstore-tool <rocksdb|leveldb> /var/lib/ceph/mon/ceph-node-1/store.db/ list

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 list

# 查看 mon 数据库中都有哪些表项
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-node-1/store.db/ list|awk '{print $1}'|uniq

# bluestore 表项前缀
# const string PREFIX_SUPER = "S";       // field -> value
# const string PREFIX_STAT = "T";        // field -> value(int64 array)
# const string PREFIX_COLL = "C";        // collection name -> cnode_t
# const string PREFIX_OBJ = "O";         // object name -> onode_t
# const string PREFIX_OMAP = "M";        // u64 + keyname -> value
# const string PREFIX_PGMETA_OMAP = "P"; // u64 + keyname -> value(for meta coll)
# const string PREFIX_PERPOOL_OMAP = "m"; // s64 + u64 + keyname -> value
# const string PREFIX_PERPG_OMAP = "p";   // u64(pool) + u32(hash) + u64(id) + keyname -> value
# const string PREFIX_DEFERRED = "L";    // id -> deferred_transaction_t
# const string PREFIX_ALLOC = "B";       // u64 offset -> u64 length (freelist)
# const string PREFIX_ALLOC_BITMAP = "b";// (see BitmapFreelistManager)
# const string PREFIX_SHARED_BLOB = "X"; // u64 offset -> shared_blob_t
# const string PREFIX_ZONED_FM_META = "Z";  // (see ZonedFreelistManager)
# const string PREFIX_ZONED_FM_INFO = "z";  // (see ZonedFreelistManager)
# const string PREFIX_ZONED_CL_INFO = "G";  // (per-zone cleaner metadata)
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ list | awk '{print $1}'| uniq

打印数据库中(kv键值对)的crc校验码

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ list-crc | grep osdmap | head -10

# 查询 kv 数量
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ list-crc | wc -l

# 查询 crc 校验码数量
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ list-crc | awk '{print $3}'| uniq | wc -l

打印 Key-Value 键值对

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 dump [prefix]

查询表项是否存在(可指定 key)

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ exists <pfefix> [key]

查询指定 value(可选是否指定输出文件)

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ get <prefix> <key> [out <file>]

# 通过 ceph-dencoder 工具反序列化解析
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-node1/store.db/ get osdmap full_999 out ./osdmap.full
ceph-dencoder import osdmap.full type OSDMap decode dump_json

查询对象大小,若不指定前缀+key,则返回整个 db 大小

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 /var/lib/ceph/osd/ceph-0/ get-size [<prefix> <key>]

更改 key 或 value

# 这条命令含义是:把指定【前缀+key】的 key 替换为 N,或者把该指定【前缀+key】的内容替换为 file
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 set <prefix> <key> [ver <N>lin <file>]

压缩数据库(缓解 rocksdb 写放大)

# rocksdb 数据分为多层,并且每层都可能有重复数据,启用 compact 可以把数据层层压缩到最底层,并且删除重复数据,起到压缩空间的作用
# 压缩整个数据库
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 compact

# 压缩指定前缀的数据
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 compact-prefix <prefix>

# 压缩指定前缀的 key=[start~end] 范围的数据
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 compact-range <prefix> <start> <end>

删除

ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 rm <prefix> <key>

# 删除整个前缀下的数据
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 rm-prefix <prefix>

备份

# path:备份到指定路径,num-keys-per-tx:每次事务中进行备份的 kv 数量
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 store-copy <path> [num-keys-per-tx] [leveldb|rocksdb|...] 

# 备份所有 kv 的 crc 
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 store-crc <path>

3. 源码分析

main

int main(int argc, const char *argv[])
{
    // 参数解析
    vector<const char*> args;
    argv_to_vec(argc, argv, args);
    
    // global 初始化
    auto cct = global_init(
        &defaults, args,
        CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY,
        CINIT_FLAG_NO_DEFAULT_CONFIG_FILE);
    common_init_finish(g_ceph_context);
    
    // 根据 store-type 初始化 storetool
    StoreTool st(type, path, to_repair, need_stats);
    
    // 根据命令行 cmd 参数,执行对应的函数
    if (cmd == "destructive-repair") {
        ... 
    }else if(cmd == "rm")
    ...
}

StoreTool

StoreTool::StoreTool(const string& type,
		     const string& path,
		     bool to_repair,
		     bool need_stats)
  : store_path(path)
{
  // 根据 type,创建 kv 句柄,其中 bluestore-kv,需要调用 bluestore->open_db_environment
  if (type == "bluestore-kv") {
    if (load_bluestore(path, to_repair) != 0)
      exit(1);
  } else {
    // 创建 kv 句柄  
    auto db_ptr = KeyValueDB::create(g_ceph_context, type, path);
  }
}

int StoreTool::load_bluestore(const string& path, bool to_repair)
{
    // 创建 bluestore
    auto bluestore = new BlueStore(g_ceph_context, path);
    KeyValueDB *db_ptr;
    // 通过 bluestore 获取 kvdb
    int r = bluestore->open_db_environment(&db_ptr, to_repair);
    db = decltype(db){db_ptr, Deleter(bluestore)};
    return 0;
}

destructive-repair

数据因为宕机发生遗失时,可以尝试通过 repair 修复,注意:没有写入日志文件的数据无法恢复。

int StoreTool::destructive_repair()
{
  return db->repair(std::cout);
}
/*目前 ceph 后端的 kvdb 一般使用 rocksdb,其修复过程分为以下4步骤:
  查找文件
  把日志转化为表
  导出metadata
  写描述符
*/

list/list-crc

void StoreTool::list(const string& prefix, const bool do_crc,
                     const bool do_value_dump)
{
  traverse(prefix, do_crc, do_value_dump,& std::cout);
}

uint32_t StoreTool::traverse(const string& prefix,
                             const bool do_crc,
                             const bool do_value_dump,
                             ostream *out)
{
  // 获取数据库迭代器
  KeyValueDB::WholeSpaceIterator iter = db->get_wholespace_iterator();
  // 如果设置了 prefix,则把迭代器调到第一次出现 prefix 的位置 
  if (prefix.empty())
    iter->seek_to_first();
  else
    iter->seek_to_first(prefix);

  uint32_t crc = -1;
  // 循环查询,直到迭代器为 invalid
  while (iter->valid()) {
    pair<string,string> rk = iter->raw_key();
    // 设置跳出循环条件
    if (!prefix.empty() && (rk.first != prefix))
      break;

    if (out)
      *out << url_escape(rk.first) << "\t" << url_escape(rk.second);
    if (do_crc) {
      bufferlist bl;
      bl.append(rk.first);
      bl.append(rk.second);
      bl.append(iter->value());
      // 计算 crc 值
      crc = bl.crc32c(crc);
      if (out) {
        *out << "\t" << bl.crc32c(0);
      }
    }
    if (out)
      *out << std::endl;
    iter->next();
  }

  return crc;
}

exist

bool StoreTool::exists(const string& prefix, const string& key)
{
  // 未指定 key,调用 StoreTool::exists(const string& prefix)
  if (key.empty()) {
    return exists(prefix);
  }
  bool exists = false;
  // 在指定 key,则调用 get(),下文介绍该方法
  get(prefix, key, exists);
  return exists;
}

bool StoreTool::exists(const string& prefix)
{
  KeyValueDB::WholeSpaceIterator iter = db->get_wholespace_iterator();
  // 通过迭代器去查询第一次出现 prefix 位置,查不到则为 invalid
  iter->seek_to_first(prefix);
  return (iter->valid() && (iter->raw_key().first == prefix));
}

get

bufferlist StoreTool::get(const string& prefix,
			  const string& key,
			  bool& exists)
{
  map<string,bufferlist> result;
  std::set<std::string> keys;
  keys.insert(key);
  // 获取指定 prefix、key 的 value,结果保存在 result
  db->get(prefix, keys, &result);

  // result 中的键值对数量大于0,则表示存在该 key 
  if (result.count(key) > 0) {
    exists = true;
    return result[key];
  } else {
    exists = false;
    return bufferlist();
  }
}

// rocksdb get 查询使用方法如下
int RocksDBStore::get(
    const string &prefix,
    const std::set <string> &keys,
    std::map <string, bufferlist> *out) {
  rocksdb::PinnableSlice value;
  if (cf_handles.count(prefix) > 0) {
    for (auto &key : keys) {
      // 若指定 prefix,则需要创建 cf(columfamily)列族句柄
      auto cf_handle = get_cf_handle(prefix, key);
      // 调用 Get 方法查询
      auto status = db->Get(rocksdb::ReadOptions(),
                            cf_handle,
                            rocksdb::Slice(key),
                            &value);
      if (status.ok()) {
        (*out)[key].append(value.data(), value.size());
      } else if (status.IsIOError()) {
        ceph_abort_msg(status.getState());
      }
      value.Reset();
    }
  } else {
    for (auto &key : keys) {
      string k = combine_strings(prefix, key);
      // 未指定 prefix,使用默认句柄:rocksdb::ColumnFamilyHandle *default_cf = nullptr;
      auto status = db->Get(rocksdb::ReadOptions(),
                            default_cf,
                            rocksdb::Slice(k),
                            &value);
      if (status.ok()) {
        (*out)[key].append(value.data(), value.size());
      } else if (status.IsIOError()) {
        ceph_abort_msg(status.getState());
      }
      value.Reset();
    }
  }
  return 0;
}

get-size

// 返回 bl 大小
bufferlist bl = st.get(prefix, key, exists);
std::cout << "(" << url_escape(prefix) << "," << url_escape(key)
              << ") size " << byte_u_t(bl.length()) << std::endl;

set

// 解析命令行参数    
    if (subcmd == "ver") {
      version_t v = (version_t) strict_strtoll(argv[7], 10, &errstr);
      encode(v, val);
    } else if (subcmd == "in") {
      int ret = val.read_file(argv[7], &errstr);
    } 
// 调用 StoreTool::set 方法
    bool ret = st.set(prefix, key, val);

bool StoreTool::set(const string &prefix, const string &key, bufferlist &val)
{
  // rocksdb 支持事务提交,步骤如下:
  /*  rocksdb::WriteBatch bat;
   *  rocksdb::WriteOptions woptions;
   *  woptions.sync = !disableWAL;
   *  auto cf = db->get_cf_handle(prefix, k);
   *  put_bat(bat, cf, k, to_set_bl);
   *  int result = submit_common(woptions, t);
  */
  KeyValueDB::Transaction tx = db->get_transaction();
  tx->set(prefix, key, val);
  int ret = db->submit_transaction_sync(tx);

  return (ret == 0);
}

store-copy

int StoreTool::copy_store_to(const string &type, const string &other_path,
                             const int num_keys_per_tx,
                             const string &other_type) 
{
  // 在参数路径位置新建或者打开 kvdb
  // open or create a leveldb store at @p other_path
  boost::scoped_ptr <KeyValueDB> other;
  KeyValueDB *other_ptr = KeyValueDB::create(g_ceph_context,
                                             other_type,
                                             other_path);
  if (int err = other_ptr->create_and_open(std::cerr); err < 0) {
    return err;
  }
  other.reset(other_ptr);
  // 获取旧 db 的迭代器
  KeyValueDB::WholeSpaceIterator it = db->get_wholespace_iterator();
  // 调整迭代器位置
  it->seek_to_first();

  // 循环提交事务,直到旧 db 的迭代器遍历完数据库
  do {
    int num_keys = 0;
    // 创建新 db 事务
    KeyValueDB::Transaction tx = other->get_transaction();

    // 通过旧迭代器查询,向新 db 的单次事务中写入 num-keys-per-tx 数量的写操作
    while (it->valid() && num_keys < num_keys_per_tx) {
      auto[prefix, key] = it->raw_key();
      bufferlist v = it->value();
      tx->set(prefix, key, v);

      num_keys++;
      total_size += v.length();

      it->next();
    }
    // 提交事务
    if (num_keys > 0)
      other->submit_transaction_sync(tx);

  } while (it->valid());

  print_summary(total_keys, total_size, total_txs, store_path, other_path,
                duration());

  return 0;
}

compact

void RocksDBStore::compact()
{
  logger->inc(l_rocksdb_compact);
  rocksdb::CompactRangeOptions options;
  // 压缩
  db->CompactRange(options, default_cf, nullptr, nullptr);
  for (auto cf : cf_handles) {
    for (auto shard_cf : cf.second.handles) {
      db->CompactRange(
	options,
	shard_cf,
	nullptr, nullptr);
    }
  }
}
 类似资料: