参考链接:
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
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>
# 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
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
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 dump [prefix]
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0/ exists <pfefix> [key]
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
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 /var/lib/ceph/osd/ceph-0/ get-size [<prefix> <key>]
# 这条命令含义是:把指定【前缀+key】的 key 替换为 N,或者把该指定【前缀+key】的内容替换为 file
ceph-kvstore-tool bluestore-kv /var/lib/ceph/osd/ceph-0 set <prefix> <key> [ver <N>lin <file>]
# 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>
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(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;
}
数据因为宕机发生遗失时,可以尝试通过 repair 修复,注意:没有写入日志文件的数据无法恢复。
int StoreTool::destructive_repair()
{
return db->repair(std::cout);
}
/*目前 ceph 后端的 kvdb 一般使用 rocksdb,其修复过程分为以下4步骤:
查找文件
把日志转化为表
导出metadata
写描述符
*/
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;
}
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));
}
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;
}
// 返回 bl 大小
bufferlist bl = st.get(prefix, key, exists);
std::cout << "(" << url_escape(prefix) << "," << url_escape(key)
<< ") size " << byte_u_t(bl.length()) << std::endl;
// 解析命令行参数
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);
}
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;
}
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);
}
}
}