RocksDB在Ceph中的调用与实现分析

RocksDB在Ceph中的调用与实现分析

目录

  1. 文档目标
  2. 整体定位
  3. 核心代码位置
  4. 抽象层KeyValueDB
  5. RocksDBStore适配层
  6. BlueStore如何接入RocksDB
  7. 数据库打开与初始化流程
  8. 写路径分析
  9. 读路径与迭代器分析
  10. 列族与分片机制
  11. MergeOperator机制
  12. BlueFS与Env的关系
  13. 缓存、统计与compact
  14. reshard重分片流程
  15. 典型调用链总结
  16. 关键结论

文档目标

这份文档不是泛泛介绍 upstream RocksDB 的 LSM 树原理,而是专门分析:

  • Ceph 里为什么要用 RocksDB
  • BlueStore 如何通过 KeyValueDB 抽象使用 RocksDB
  • RocksDBStore 如何把 Ceph 的 prefix/key 模型映射到底层 RocksDB
  • 事务、读写、迭代、列族、分片、merge、compact、重分片这些机制在 Ceph 中是怎么落地的

如果你正在阅读这些文件:

  • cephMain/src/kv/KeyValueDB.h
  • cephMain/src/kv/KeyValueDB.cc
  • cephMain/src/kv/RocksDBStore.h
  • cephMain/src/kv/RocksDBStore.cc
  • cephMain/src/os/bluestore/BlueStore.cc

那么这份文档的目标就是把它们串成一条完整主线。


整体定位

在 Ceph 的 BlueStore 架构中,对象数据 主要直接落到块设备上,而 元数据 则主要存放在 RocksDB 中。

可以把它粗略理解为:

  • BlueStore 负责对象语义、空间管理、缓存、事务组织
  • RocksDB 负责元数据的 KV 持久化
  • BlueFS 负责为 RocksDB 提供底层文件存储环境

它们之间的分工如下:

层次 角色 主要职责
OSD/BlueStore 上层调用者 发起对象元数据读写、事务提交、迭代扫描
KeyValueDB 抽象层 定义统一 KV 接口,屏蔽具体后端
RocksDBStore 适配层 把 Ceph 接口映射为 RocksDB API
RocksDB 元数据引擎 管理 memtable、WAL、SST、compaction、迭代器
BlueFS / Env 存储环境 提供 RocksDB 文件读写运行环境

因此,从 Ceph 代码视角看,真正值得重点理解的,不是 RocksDB 全部内部实现,而是:

  1. KeyValueDB 抽象语义
  2. RocksDBStore 如何实现这个抽象
  3. BlueStore 如何调用 KeyValueDB

核心代码位置

1. 抽象接口层

  • cephMain/src/kv/KeyValueDB.h
  • cephMain/src/kv/KeyValueDB.cc

这层定义了通用 KV 数据库接口,包括:

  • 事务对象 TransactionImpl
  • 读取接口 get()
  • 迭代器接口 IteratorImpl / WholeSpaceIteratorImpl
  • merge operator 抽象
  • compact、缓存、统计等可选能力

2. RocksDB适配层

  • cephMain/src/kv/RocksDBStore.h
  • cephMain/src/kv/RocksDBStore.cc

这层把上面的抽象映射为 RocksDB 的:

  • rocksdb::DB
  • rocksdb::WriteBatch
  • rocksdb::Iterator
  • rocksdb::ColumnFamilyHandle
  • rocksdb::Options
  • rocksdb::Env

3. BlueStore接入层

  • cephMain/src/os/bluestore/BlueStore.cc

BlueStore 通过 KeyValueDB::create() 创建数据库实例,通过 db->get_transaction()db->submit_transaction()db->get()db->get_iterator() 等接口完成元数据操作。


抽象层KeyValueDB

KeyValueDB 的核心价值是:把 Ceph 上层的元数据访问模型统一成 prefix/key 语义

1. prefix + key 模型

Ceph 不是简单把所有元数据塞进一个平面的 key 空间,而是按逻辑类别分前缀管理。例如:

  • 某些前缀用于对象元数据
  • 某些前缀用于统计信息
  • 某些前缀用于 omap
  • 某些前缀用于 allocator / freelist 相关元数据

所以 KeyValueDB 大部分接口都长这样:

1
2
3
4
get(prefix, key, value)
set(prefix, key, value)
rmkey(prefix, key)
get_iterator(prefix)

这意味着调用者思考的是“逻辑前缀 + 逻辑 key”,而不是 RocksDB 原始键编码细节。

2. 事务抽象

TransactionImpl 表示一批待提交的写操作,其职责是:

  • 累积 set
  • 累积 rmkey
  • 累积 rm_range_keys
  • 累积 merge
  • 暴露操作数与字节规模

这层并不规定底层必须怎么实现,只规定“提交前是一批内存中的写集合,提交后一次性生效”。

RocksDBStore 中,这个抽象最终会映射成 rocksdb::WriteBatch

3. 迭代器抽象

KeyValueDB 定义了两类迭代器:

  • IteratorImpl
    面向 prefix 过滤后的逻辑视图
  • WholeSpaceIteratorImpl
    面向底层实现暴露的原始全键空间

这种两层设计非常重要,因为:

  • 上层通常只关心某个 prefix 下的 key
  • 实现层有时需要直接处理原始编码键,例如 "prefix\0key"
  • 当存在多个 column family / shard 时,实现层还可能需要做跨迭代器合并

4. MergeOperator抽象

KeyValueDB::MergeOperator 是对 RocksDB associative merge 的抽象包装。

它定义:

  • merge_nonexistent()
  • merge()
  • name()

其意义是:

  • Ceph 上层只注册逻辑 merge 规则
  • 具体怎么挂接到 RocksDB 默认 CF 或独立 CF,由 RocksDBStore 负责

RocksDBStore适配层

RocksDBStore 是这套链路的核心。

它解决的问题是:

Ceph 的 prefix/key 抽象,如何安全且高效地映射到 RocksDB 的 DB / CF / Iterator / WriteBatch / Options / Env 体系。

1. 它管理的关键对象

RocksDBStore 内部最重要的成员包括:

  • rocksdb::DB *db
    真正的 RocksDB 数据库实例
  • rocksdb::Env *env
    底层运行环境,可能是默认文件系统,也可能是 BlueRocksEnv
  • rocksdb::BlockBasedTableOptions bbt_opts
    block cache、filter、index 等表选项
  • default_cf
    默认列族句柄
  • cf_handles
    逻辑 prefix 到实际列族分片句柄的路由表
  • cf_ids_to_prefix
    用于把 RocksDB column family id 反查回 prefix
  • dbstats
    RocksDB 统计对象

2. 它完成的核心映射

映射一:事务抽象 -> WriteBatch

KeyValueDB::TransactionImpl

映射成:

RocksDBStore::RocksDBTransactionImpl

其中核心成员就是:

  • rocksdb::WriteBatch bat

映射二:逻辑key -> RocksDB原始key

有两种模式:

  1. 没有独立列族时
    原始 key = "prefix\0key"

  2. 有独立列族时
    prefix 直接决定列族,列族内只保存裸 key

映射三:prefix -> ColumnFamily

若 prefix 被配置为 column family,则:

  • prefix 直接映射到某个 CF
  • 如果该 prefix 进一步分片,则再根据 key 的 hash 路由到某个 shard CF

映射四:全空间迭代 -> prefix迭代

底层先提供 whole-space iterator,再按 prefix 做过滤包装;
如果实现层知道某个 prefix 已经独占某个 CF,则可以直接用更高效的单 CF iterator。


BlueStore如何接入RocksDB

BlueStore 自己并不直接 new rocksdb::DB,而是通过 KeyValueDB 工厂来创建。

1. 创建入口

KeyValueDB.cc 的工厂非常简单:

1
2
3
4
5
6
7
8
9
10
KeyValueDB *KeyValueDB::create(CephContext *cct, const string& type,
const string& dir,
map<string,string> options,
void *p)
{
if (type == "rocksdb") {
return new RocksDBStore(cct, dir, options, p);
}
return NULL;
}

因此 BlueStore 只需要提供:

  • 类型字符串 "rocksdb"
  • 路径
  • KV 选项
  • 可选 Env 指针

就能拿到 RocksDBStore 实例。

2. BlueStore中的典型初始化动作

BlueStore 在初始化数据库时,通常会做三件事:

  1. 调用 KeyValueDB::create()
  2. 调用 db->set_merge_operator() 注册某些 prefix 的 merge 规则
  3. 调用 db->open()db->create_and_open()

这意味着:

  • KeyValueDB 负责统一入口
  • RocksDBStore 负责真实数据库打开
  • merge operator 必须在 open 前就注册完成

3. 运行期调用方式

BlueStore 后续主要通过以下接口使用 RocksDB:

  • get_transaction()
  • submit_transaction()
  • submit_transaction_sync()
  • get()
  • get_iterator()
  • compact_*()
  • estimate_prefix_size()

也就是说,BlueStore 使用的是“数据库抽象”,而非 RocksDB 原生 API。


数据库打开与初始化流程

RocksDBStore 的打开主流程核心在 do_open()

1. init阶段

init() 做的事情很克制:

  • 保存 options 字符串
  • 试解析一遍配置,尽早发现错误

不真正打开数据库

这样 BlueStore 在 mount 前就能先验证 RocksDB 参数是否合法。

2. load_rocksdb_options阶段

load_rocksdb_options() 是选项构造中心,负责:

  • 解析文本配置串
  • 安装 Ceph 日志器
  • 绑定 Env
  • 配置 block cache / row cache
  • 配置 bloom filter / index type / table factory
  • 安装默认 CF 的 merge router
  • 配置 RocksDB statistics

可以理解为:这里把 Ceph 配置世界转换成 RocksDB Options 世界。

3. create_and_open路径

如果数据库尚不存在,则:

  1. 创建目录
  2. 打开 RocksDB
  3. 应用 column family / sharding 布局
  4. 持久化 sharding/def

4. open已有数据库路径

如果是打开已有数据库,则:

  1. 读取持久化的 sharding 定义
  2. 校验 RocksDB 实际列族与目标布局是否一致
  3. 打开已有列族
  4. 如处于重分片恢复阶段,则补建缺失列族
  5. 建立运行期 cf_handles 路由表

这里最关键的一点是:

Ceph 在 reopen 时不是“盲开 RocksDB”,而是先验证逻辑分片布局与物理列族布局是否匹配。


写路径分析

1. 从BlueStore到事务对象

BlueStore 需要写元数据时,先拿一个事务:

1
2
3
4
BlueStore
-> db->get_transaction()
-> RocksDBStore::RocksDBTransactionImpl
-> rocksdb::WriteBatch bat

此时所有写操作只是追加到 WriteBatch 里。

2. set路径

调用 set(prefix, key, value) 时,RocksDBTransactionImpl::set() 会先判断:

  • 这个 prefix 是否有独立 CF
  • 如果有,进一步判断是否要按 shard 分片

然后有两种写法:

情况一:prefix 落在 default CF

把 key 编码成:

prefix + '\0' + key

然后写入 default_cf

情况二:prefix 落在独立CF

直接把裸 key 写入目标 CF

3. 删除路径

删除有三种典型方式:

  • rmkey()
  • rmkeys_by_prefix()
  • rm_range_keys()

其中 rmkeys_by_prefix()rm_range_keys() 里有一个非常重要的优化:

  • 如果删除键数较少,则逐 key Delete
  • 如果删除范围很大,则改用 DeleteRange

这样做的目的是在:

  • 精确性
  • 写放大
  • tombstone 数量

之间做平衡。

4. merge路径

merge() 不会立即读出旧值再合并,而是把 merge 操作写入 RocksDB,由 merge operator 在后续读取或 compaction 时解释。

这适合:

  • 计数器
  • 统计项
  • 增量聚合类元数据

5. 提交路径

所有事务最后都进入:

  • submit_transaction()
  • submit_transaction_sync()

它们最终都会调用:

  • submit_common()

submit_common() 负责:

  1. 设置 rocksdb::WriteOptions
  2. 根据配置决定是否禁用 WAL
  3. 打印事务内容用于调试
  4. 调用 db->Write(woptions, &bat)
  5. 采集 RocksDB perf context 数据

因此,Ceph 事务和 RocksDB WriteBatch 的关系可以概括为:

一个 Ceph KV 事务,最终就是一次 RocksDB db->Write()


读路径与迭代器分析

1. get路径

RocksDBStore::get() 的主要工作依然是路由:

  1. 判断 prefix 是否映射独立 CF
  2. 若需要,则进一步选择 shard
  3. 调用 RocksDB Get

同样分两种情况:

  • default CF:读 "prefix\0key"
  • 独立 CF:读裸 key

2. split_key的意义

默认 CF 中,原始键是组合键:

prefix\0key

因此 split_key() 是一个很基础但非常重要的辅助函数。

它的作用是把 RocksDB 原始 key 拆回:

  • prefix
  • 逻辑 key

很多迭代器逻辑都依赖它。

3. WholeSpaceIterator

WholeSpaceIteratorImpl 表示底层数据库实现直接暴露的原始迭代器。

在 RocksDB 场景下,它通常对应:

  • 某个 column family 上的 rocksdb::Iterator

它能返回:

  • key()
  • raw_key()
  • value()
  • value_as_sv()

这里的 raw_key() 特别重要,因为它可能是未过滤 prefix 的真实编码键。

4. PrefixIterator

KeyValueDB 默认提供一个 PrefixIteratorImpl 包装器,它的作用是:

  • 底层先拿 whole-space iterator
  • 外层再按 prefix 过滤

这保证即使某个后端没有实现更高级的原生 prefix iterator,也能对上层暴露统一行为。

5. RocksDBStore中的优化

RocksDBStore 不只是使用通用 PrefixIteratorImpl,它还会根据:

  • prefix 是否独占某个列族
  • 迭代边界是否落在单个 shard

来决定是否直接创建更高效的单列族 iterator。

这就是为什么 RocksDBStore 里会有:

  • new_shard_iterator()
  • check_cf_handle_bounds()
  • 跨 shard 的 merge iterator

列族与分片机制

这是 Ceph 对 RocksDB 使用方式里最关键、也最“非通用 RocksDB 教材式”的部分。

1. 为什么需要列族

如果所有 prefix 都放在 default CF 中,会遇到几个问题:

  • 不同类型元数据混在一起
  • compaction 相互影响
  • 缓存难以差异化
  • 热点前缀容易形成瓶颈

因此 Ceph 支持把某些 prefix 提升为独立 column family。

2. 为什么还需要分片

即使一个 prefix 独占一个 CF,仍可能有问题:

  • 某个前缀的数据量太大
  • 某个前缀写入太热
  • 单 CF compaction 压力太集中

因此 Ceph 允许一个逻辑列族继续拆成多个 shard:

1
O -> O-0, O-1, O-2, O-3 ...

3. 分片定义字符串

Ceph 使用文本字符串描述 sharding 布局,例如:

1
O(4,0-10)=write_buffer_size=...

它描述的是:

  • 逻辑列族名
  • shard 数量
  • 用 key 的哪一段参与 hash
  • 该列族附加的 RocksDB 选项

parse_sharding_def() 就是这个语法的解析器。

4. 路由规则

路由过程是:

  1. 通过 prefix 找到逻辑列族
  2. 如果只有一个 handle,直接落该 CF
  3. 如果有多个 shard,则取 key 的某个子串做 hash
  4. hash % shard_count 选出最终 shard

5. 运行期持久化

Ceph 会把当前 sharding 定义写到:

  • sharding/def

这样下次重启时可以:

  • 校验 RocksDB 中真实存在的列族
  • 恢复重分片状态
  • 拒绝错误布局的数据库

BlueStore各Prefix的Key/Value格式总表

下面这张表总结的是 BlueStore 传给 KeyValueDB 的逻辑格式

真正落到 RocksDB 时,RocksDBStore 还会再做一层物理映射:

  • 若该 prefix 落在 default CF,则物理 key = prefix + '\0' + logical_key
  • 若该 prefix 有独立 CF,则物理 key = logical_key

因此表中重点关注的是 BlueStore 自己定义的 logical_keyvalue 编码规则。

Prefix 主要用途 logical key 格式 value 格式 说明
S super 元数据 通常是可读字段名,如 nid_maxblobid_maxondisk_formatmin_alloc_size 对应字段的 encode() 后 bufferlist 保存 BlueStore 全局配置、版本、计数器、高水位等
T 统计信息 可读字段名,或 pool_id 的二进制编码 key 统计结构编码后的 bufferlist,常通过 merge 更新 BLUESTORE_GLOBAL_STATFS_KEY 也存放在这里
C collection 元数据 stringify(cid) cnode_t 的编码 保存 collection 的 bits 等元数据
O onode / 对象元数据 ghobject_t 的排序友好二进制编码:shard + pool + hash + namespace + key/name + snap + generation + 'o' bluestore_onode_t + spanning blobs + inline extents 编码后的 bufferlist BlueStore 最核心的一类记录
M 普通 omap u64 nid + '.' + user_key;边界键分别用 '-''~' header/tail 特殊值,普通项为用户 omap value 默认对象 omap
P pgmeta omap u64 nid + '.' + user_key;不带 pool/hash 前缀 M 相同 用于 meta collection 的 omap
m per-pool omap u64 pool + u64 nid + '.' + user_key M 相同 把 pool 维度编码进 key 前缀
p per-pg omap u64 pool + u32 hash + u64 nid + '.' + user_key M 相同 比 per-pool 再多一层 hash 维度
L deferred 事务 u64 seq deferred_transaction_t 的编码 启动回放 deferred write 就扫这类键
X shared blob 元数据 u64 sbid bluestore_shared_blob_t 的编码 保存 shared blob 引用关系等元数据
B freelist 空间管理 u64 offset u64 length 经典 freelist 形式,表示某段空闲区间
b bitmap freelist 空间管理 BitmapFreelistManager 定义的位图键 位图相关结构编码 属于 bitmap allocator 的内部格式

1. S:super元数据

这类记录最直观,key 往往就是字段名,value 是对应字段的编码结果。例如:

  • nid_max
  • blobid_max
  • freelist_type
  • ondisk_format
  • min_compat_ondisk_format
  • min_alloc_size
  • per_pool_omap

它们的共同特点是:

  • key 可读
  • value 一般是定长标量或简单结构的 encode() 结果
  • 用于 BlueStore 启动时恢复全局状态

2. T:统计信息

T 类记录主要用于:

  • 全局 statfs
  • per-pool 统计

这里有两类常见 key:

  1. 可读字符串 key,例如:
    • bluestore_statfs
  2. 二进制 pool 统计 key:
    • u64(pool_id)

这类 value 常不是“整块重写”,而是通过 merge() 增量更新。

3. C:collection元数据

C 类记录较简单:

  • key = stringify(cid)
  • value = cnode_t

典型作用是保存:

  • collection 的 bits
  • collection 的基础拓扑元信息

4. O:对象onode元数据

O 是最重要的一类。

key 格式

对象 key 是按排序需求定制的二进制编码,核心顺序是:

  1. shard
  2. pool
  3. hash
  4. namespace
  5. key/object name
  6. snap
  7. generation
  8. 后缀字符 'o'

它的目标不是人可读,而是:

  • 能按对象逻辑顺序扫描
  • 能支持范围列举
  • 能支持 lower_bound/upper_bound

value 格式

O 类 value 由三部分拼接而成:

  1. bluestore_onode_t
  2. spanning blob 信息
  3. inline extent map 数据

如果 extent map 已经分片,则分片内容不一定都 inline 放在这个 value 里。

5. M/P/m/p:omap家族

omap 的关键点是:底层 key 不是直接等于用户 omap key

它会在用户 key 前面加上对象/池/PG 相关前缀,以便:

  • 把同一个对象的 omap 放在连续范围里
  • 构造 header/key/tail 三种边界

三类边界字符是:

  • '-':header
  • '.':实际用户 key
  • '~':tail

所以同一个对象的 omap 区间通常长这样:

1
2
3
... + nid + '-'
... + nid + '.' + user_key
... + nid + '~'

不同变种区别在于 nid 前面是否再编码:

  • M:只有 nid
  • P:pgmeta 场景,也只有 nid
  • mpool + nid
  • ppool + hash + nid

6. L:deferred事务

L 类记录表示尚未完全回放/清理的 deferred 写事务。

  • key = u64 seq
  • value = deferred_transaction_t

启动恢复时,BlueStore 会扫描 PREFIX_DEFERRED,逐条回放。

7. X:shared blob

X 类记录保存 shared blob 元数据。

  • key = u64 sbid
  • value = bluestore_shared_blob_t

value 里最关键的是:

  • shared blob 标识
  • ref_map
  • 引用计数相关状态

8. B:传统freelist

B 类记录表示空闲空间区间:

  • key = u64 offset
  • value = u64 length

它描述的语义很直接:

offset 开始,有一段长度为 length 的空闲物理空间。

9. b:bitmap freelist

b 类记录属于 bitmap allocator 的内部格式。

它和 B 的区别是:

  • B 偏向“区间列表”
  • b 偏向“位图块”

这里的 key/value 具体布局不在 BlueStore.cc 里定义,而由 BitmapFreelistManager 负责。
如果你继续往下看 allocator 路径,重点就该转到 bitmap freelist 的实现文件。


MergeOperator机制

1. 为什么要做两层适配

RocksDB 每个 column family 只能配置一个 merge operator。

但 Ceph 的逻辑模型是:

  • 不同 prefix 可能需要不同 merge 语义

如果所有这些 prefix 都落在 default CF,就会出现冲突:

  • default CF 里混着多个 prefix
  • 每个 prefix 却想用不同 merge 规则

因此 RocksDBStore 设计了两种适配器:

2. MergeOperatorRouter

用于 default CF。

它的逻辑是:

  1. 从原始 key 中识别 prefix
  2. 在已注册的 merge operator 列表中找到对应 prefix
  3. 把 merge 委托给对应 Ceph MergeOperator

3. MergeOperatorLinker

用于非 default CF。

因为独立 CF 对应单一逻辑 prefix,所以不需要“路由”,只需要直接绑定。

4. 为什么要构造稳定名称

RocksDB 在打开数据库时会校验 merge operator 名称是否一致。

因此 RocksDBStore 会把:

  • prefix
  • operator name

组合成稳定的整体名字,以保证:

  • 注册顺序变化不会影响最终名称
  • open 时能正确做一致性检查

BlueFS与Env的关系

1. Env是什么

rocksdb::Env 是 RocksDB 对底层文件环境的抽象。

RocksDB 的很多操作并不直接调用 POSIX 文件 API,而是通过 Env

  • 创建文件
  • 读写 WAL
  • 读写 SST
  • 创建目录
  • 后台线程

2. Ceph为什么要定制Env

在 BlueStore 场景里,RocksDB 文件并不一定落在普通文件系统目录中,而可能落在:

  • BlueFS 管理的设备空间

因此 BlueStore 会在合适条件下构造:

  • BlueRocksEnv

再把它作为 void* p 传给 KeyValueDB::create()

然后 RocksDBStore 在构造或 open 时把它转回:

  • rocksdb::Env *env

3. 结果是什么

RocksDB 仍以为自己在使用标准 Env
但实际底层文件已经由 BlueFS 承载。

这使得 Ceph 实现了:

  • RocksDB 逻辑不大改
  • BlueFS 接管底层存储
  • WAL/SST 与 BlueStore 设备布局协同工作

缓存、统计与compact

1. 缓存

RocksDBStore::load_rocksdb_options() 里会统一构造缓存体系:

  • block cache
  • row cache
  • bloom filter
  • index type

Ceph 还支持:

  • 全局 RocksDB cache 大小配置
  • 某些列族单独 block cache 配置
  • 优先级缓存接口 PriorityCache

这说明 Ceph 并不只是“用一下 RocksDB”,而是在缓存资源分配上做了比较深的整合。

2. 统计

RocksDBStore 支持:

  • RocksDB statistics
  • RocksDB perf context
  • Ceph perf counters

因此既能看:

  • RocksDB 内部写 WAL / memtable / delay 的耗时

也能看:

  • Ceph 视角的 get latency / submit latency / compact 指标

3. compact

Ceph 暴露了多种 compact 入口:

  • 全量 compact
  • prefix compact
  • range compact
  • async compact

RocksDBStore 还维护了一个后台 compact 队列,用来:

  • 合并相邻 compact 范围
  • 避免前台线程直接承担长时间 compact

reshard重分片流程

reshard 是 RocksDBStore 里最复杂的一类维护逻辑之一。

它的本质是:

把已有数据库中的 key,从旧的列族布局搬迁到新的列族布局。

1. 为什么需要reshard

因为随着系统运行,最初的列族布局可能不再适合:

  • 数据量变大
  • 某类前缀成为热点
  • compaction 压力集中
  • 需要新的缓存策略

2. reshard的核心步骤

  1. 解析目标 sharding 定义
  2. 写入重分片锁
  3. 列出现有列族
  4. 打开所有旧列族
  5. 计算缺失的新列族并创建
  6. 构造新的 cf_handles
  7. 遍历旧列族全部 key
  8. 根据新规则重算目标 CF
  9. 批量执行 Delete + Put
  10. 删除旧布局中不再需要的空列族
  11. 写回新的 sharding/def

3. 为什么实现里有很多“失败注入”开关

resharding_ctrl 里有:

  • unittest_fail_after_first_batch
  • unittest_fail_after_processing_column
  • unittest_fail_after_successful_processing

这是因为重分片是一个跨多步的维护过程,最怕中途失败导致状态不一致。

所以实现必须支持:

  • 故障注入
  • 启动后恢复
  • 继续完成中断的重分片

典型调用链总结

1. 初始化调用链

1
2
3
4
5
6
7
BlueStore
-> KeyValueDB::create("rocksdb", ...)
-> new RocksDBStore(...)
-> set_merge_operator(...)
-> open() / create_and_open()
-> do_open()
-> rocksdb::DB::Open(...)

2. 写事务调用链

1
2
3
4
5
6
7
BlueStore
-> db->get_transaction()
-> tx->set()/rmkey()/merge()
-> RocksDBTransactionImpl::bat 累积写操作
-> db->submit_transaction()
-> RocksDBStore::submit_common()
-> rocksdb::DB::Write()

3. 单key读取调用链

1
2
3
4
5
BlueStore
-> db->get(prefix, key, &value)
-> RocksDBStore::get()
-> 判断 prefix 对应的 CF / shard
-> rocksdb::DB::Get()

4. 迭代器调用链

1
2
3
4
5
BlueStore
-> db->get_iterator(prefix)
-> RocksDBStore::get_iterator()
-> 选择 whole-space / shard iterator / merge iterator
-> rocksdb::Iterator

5. merge调用链

1
2
3
4
5
6
7
BlueStore
-> db->set_merge_operator(prefix, op)
-> RocksDBStore open 前安装 merge operator
-> tx->merge(prefix, key, value)
-> RocksDB WriteBatch::Merge()
-> Router/Linker
-> 具体 Ceph MergeOperator

关键结论

1. KeyValueDB是Ceph真正依赖的接口

Ceph 上层并不直接依赖 RocksDB 原生 API,而是依赖 KeyValueDB 抽象。

2. RocksDBStore是关键桥梁

RocksDBStore 负责把:

  • Ceph 的 prefix/key 模型
  • 事务模型
  • 迭代器模型
  • merge 模型

映射到 RocksDB 的对象体系中。

3. Ceph对RocksDB的使用并不“浅”

Ceph 并不是简单把 RocksDB 当成一个黑盒 KV 库,而是深度利用了:

  • column family
  • merge operator
  • Env
  • WriteBatch
  • iterator bounds
  • perf context
  • block cache
  • DeleteRange
  • 重分片

4. BlueFS是RocksDB接入BlueStore设备体系的关键

没有 BlueFS / BlueRocksEnv,RocksDB 只能把文件落到普通文件系统;
有了它,Ceph 才能把 RocksDB WAL/SST 纳入 BlueStore 的设备管理体系。

5. 读这部分代码的最佳顺序

建议按下面顺序阅读:

  1. KeyValueDB.h
  2. KeyValueDB.cc
  3. RocksDBStore.h
  4. RocksDBStore.cc
  5. BlueStore.cc 中数据库初始化、读写事务、iterator 调用部分

如果按这个顺序看,理解成本会明显低很多。

文章互动

阅读 --

留言

0 条留言

正在加载留言…