当前位置: 首页 > news >正文

Ceph BlueStore 元数据全景:一个 OSD 的 RocksDB 里到底存了什么?

本文基于 Ceph 源码(os/bluestore/bluestore_types.hos/bluestore/BlueStore.hos/bluestore/BlueStore.ccos/bluestore/BlueFS.h)深入分析 BlueStore 的元数据存储架构,完整回答"一个 OSD 的 BlueStore 上到底存了哪些元数据"这一问题。


一、BlueStore 的整体架构

每个 OSD 进程内部运行一个 BlueStore 实例,BlueStore 是 Ceph 从 FileStore 演进到的新型存储引擎。核心设计思想:元数据全部进 RocksDB,用户数据直接写裸盘

OSD 进程 ├── BlueStore (ObjectStore 实现) │ ├── bdev[BDEV_WAL] → block.wal (WAL 专用裸盘分区) │ ├── bdev[BDEV_DB] → block.db (RocksDB 专用裸盘分区) │ ├── bdev[BDEV_SLOW] → block.slow (可选,RocksDB 溢出空间) │ ├── bdev[data] → block (主数据裸盘分区) │ ├── BlueFS (简易文件系统,管理 WAL/DB/Slow 设备上的文件) │ └── RocksDB (通过 BlueRocksEnv 读写 BlueFS 上的文件) │ └── 存储所有元数据 └── 主数据盘 block (直接裸盘 IO,不走 BlueFS/RocksDB)

BlueFS 是一个极简的文件系统,专门为 RocksDB 服务。它管理block.walblock.dbblock.slow三个裸盘分区上的文件读写。RocksDB 通过自定义的BlueRocksEnv(实现了 rocksdb::Env 接口)来访问 BlueFS 的文件,而不是操作宿主机的 ext4/xfs 文件系统。

这样做的好处是:RocksDB 的 WAL(Write-Ahead Log)可以写到更快的小盘(NVMe),SST 文件写到中等速度的盘,溢出部分再写到慢盘——三级存储分层,按速度和成本分配。

而用户数据(对象的 payload)则完全绕过 RocksDB,通过 AIO 直接写到主数据裸盘(block设备),只有"数据在裸盘的物理位置"这条元数据才进 RocksDB。


二、元数据的分类:9 个 Key Prefix

BlueStore 把 RocksDB 的 key 按前缀(prefix)分成 9 个 namespace,每个 prefix 存不同类型的元数据。源码定义在BlueStore.cc第 70~80 行:

conststring PREFIX_SUPER="S";// field -> valueconststring PREFIX_STAT="T";// field -> value(int64 array)conststring PREFIX_COLL="C";// collection name -> cnode_tconststring PREFIX_OBJ="O";// object name -> onode_tconststring PREFIX_OMAP="M";// u64 + keyname -> valueconststring PREFIX_PGMETA_OMAP="P";// u64 + keyname -> value(for meta coll)conststring PREFIX_DEFERRED="L";// id -> deferred_transaction_tconststring PREFIX_ALLOC="B";// u64 offset -> u64 length (freelist)conststring PREFIX_ALLOC_BITMAP="b";// (see BitmapFreelistManager)conststring PREFIX_SHARED_BLOB="X";// u64 offset -> shared_blob_t

下面逐个详细解析。


三、Super(S)—— 全局配置信息

key: "S" + field_name value: 全局字段值

这是 BlueStore 的 superblock,存 OSD 级别的全局配置信息:

  • FSID:Ceph 集群 ID
  • OSD UUID:该 OSD 的唯一标识
  • last_seq:最后的事务序列号

类似传统文件系统的 superblock,在 OSD 启动时读取,用于验证设备归属和恢复状态。


四、Stat(T)—— 统计信息

key: "T" + field_name (如 "bluestore_statfs") value: int64 数组

存 OSD 级别的空间统计信息(store_statfs_t),包括:

  • 已用空间大小
  • 可用空间大小
  • 对象总数

这些数据会定期更新,对外通过ceph osd df命令展示。本质上是"这个 OSD 的磁盘空间用了多少、还剩多少"的底层记录。


五、Collection(C)—— 集合元数据

key: "C" + collection_name value: bluestore_cnode_t { bits }

Collection 是 PG(Placement Group)的等价概念。cnode_t的结构极其精简(bluestore_types.h第 52~64 行):

structbluestore_cnode_t{uint32_tbits;///< how many bits of coll pgid are significant};

只有一个字段bits:PGID 的有效哈希位数。它控制了 PG 的分裂粒度——当 PG 数量增长时,bits 增大,一个 Collection 可以分裂成更细的子集合。

Collection 虽然元数据很小,但它是对象组织的"容器":所有对象都挂在一个 Collection 下面,读写对象时先定位 Collection。


六、Onode(O)—— 对象元数据(最核心的部分)

key: "O" + collection_hash + object_hash value: bluestore_onode_t

这是 BlueStore 中最重要的元数据类型,类似文件系统的 inode。源码定义(bluestore_types.h第 898~970 行):

structbluestore_onode_t{uint64_tnid=0;///< numeric id (locally unique)uint64_tsize=0;///< object sizestd::map<string,buffer::ptr>attrs;///< xattrsstructshard_info{uint32_toffset=0;///< logical offset for start of sharduint32_tbytes=0;///< encoded bytes};vector<shard_info>extent_map_shards;///< extent map shards (if any)uint32_texpected_object_size=0;uint32_texpected_write_size=0;uint32_talloc_hint_flags=0;uint8_tflags=0;// FLAG_OMAP = 1, FLAG_PGMETA_OMAP = 2};

逐字段解释:

字段含义
nid对象数字 ID,OSD 内唯一,用于 omap key 的前缀
size对象的逻辑大小(字节)
attrs对象的扩展属性(xattr)——键值对,存用户/系统属性。类似文件系统的 xattr
extent_map_shardsextent map 分片索引。小对象 inline 在 onode 里,大对象分片存储
expected_object_size分配提示:上层期望的对象大小(RBD/CephFS 传来)
expected_write_size分配提示:期望的写大小
alloc_hint_flags分配提示标志(如SEQUENTIAL、RANDOM)
flagsFLAG_OMAP 表示有 omap 数据

注意:onode 的 key 是collection_hash + object_hash的组合,不是对象名本身。这样做可以高效地按 Collection 扫描所有对象(RocksDB 的 key 是有序的)。

内存中,Onode 还关联一个ExtentMap(BlueStore.h 第 1054 行):

structOnode{ghobject_t oid;///< 对象名(内存中才有)string key;///< RocksDB keybluestore_onode_t onode;///< 持久化的元数据boolexists;///< 对象是否逻辑存在ExtentMap extent_map;///< 数据布局映射(内存中展开)};

七、Extent Map + Blob —— 数据布局与校验和(layout 和 CRC)

这是本文的核心问题——“对象的 layout 信息和 CRC 信息在哪里?”

7.1 Extent Map 的概念

Extent Map 是 onode 下面的子结构,描述"对象的逻辑字节范围 → 物理存储位置"的映射关系。类似文件系统的 extent 映射(ext4 的 extent tree、XFS 的 extent map)。

对象逻辑空间:offset 0 ... size-1 ↓ Extent Map 映射 裸盘物理空间:pextent {offset, length}

对于小对象,Extent Map 直接 inline 在 onode 的 value 里(一条 KV);对于大对象,Extent Map 被分成多个 shard,每个 shard 是一条独立的 RocksDB KV。

7.2 Blob 的结构(layout + CRC 都在这里)

每个 Extent 引用一个 Blob。bluestore_blob_t是 layout 和 CRC 的共同载体(bluestore_types.h第 429~862 行):

structbluestore_blob_t{PExtentVector extents;///< 裸盘上的物理位置!这就是 layoutuint32_tlogical_length=0;///< 逻辑长度uint32_tcompressed_length=0;///< 压缩后长度uint32_tflags=0;///< FLAG_COMPRESSED/CSUM/SHARED/HAS_UNUSEDuint8_tcsum_type=Checksummer::CSUM_NONE;///< 校验和类型uint8_tcsum_chunk_order=0;///< chunk 大小 = 1 << orderbufferptr csum_data;///< 校验值数组(真正的 CRC 数据)unused_t unused=0;///< 未使用区域位图};

关键字段详解

extents(PExtentVector)—— 对象的 layout 信息
structbluestore_pextent_t:publicbluestore_interval_t<uint64_t,uint32_t>{uint64_toffset;// 裸盘物理偏移uint32_tlength;// 物理长度};

每个 pextent 就是一个{offset, length}对,告诉你"这块数据写在主数据裸盘的哪个位置、多长"。一个 Blob 可以有多个 pextent(比如数据被分散写到多个不连续的位置)。

这就是你问的"对象的 layout 信息"——它存在 RocksDB 里,不在裸盘上。

csum_data —— 对象的 CRC 信息
uint8_tcsum_type;// 校验和类型:CRC32、XXHASH32 等uint8_tcsum_chunk_order;// chunk 大小 = 2^order 字节bufferptr csum_data;// 校验值数组

CRC 也是按 chunk 计算的,不是整个 Blob 一个校验值。csum_chunk_order决定了每个 chunk 多大(比如 order=12 → chunk=4096 字节),csum_data是一个紧凑的数组,每csum_value_size字节存一个 chunk 的校验值。

这就是你问的"CRC 信息"——它也存在 RocksDB 里,不在裸盘数据块旁边!

flags —— Blob 的特征标记
Flag含义
FLAG_COMPRESSED2Blob 数据被压缩了,compressed_length有效
FLAG_CSUM4Blob 有校验和,csum_*字段有效
FLAG_HAS_UNUSED8Blob 有未写入区域(unused位图有效)
FLAG_SHARED16Blob 被多个对象共享(去重场景,见 SharedBlob)
unused —— Blob 内的空洞位图

当一个 Blob 被分配了空间但部分区域还没写入数据时,unused位图标记哪些 chunk 是"空洞"(从未写入)。这在稀疏写场景下非常有用——可以避免对空洞区域做不必要的读-改-写。

7.3 为什么要把 layout 和 CRC 都放 RocksDB?

这是一个深思熟虑的设计决策。如果像传统文件系统那样把 CRC 写到数据块旁边(每个数据块自带 header),会带来几个问题:

  1. 原子性:RocksDB 提供事务性写入。layout 更新 + CRC 更新 + 空间分配记录更新可以在同一个 RocksDB transaction 中完成。如果 CRC 写到裸盘上,layout 更新写到 RocksDB 里,两者无法在同一事务中提交,可能导致"layout 是新的、CRC 是旧的"这种不一致。

  2. 灵活性:CRC 和 layout 放在 KV 存储里,可以独立更新。比如做压缩时,只需要修改 Blob 的compressed_lengthcsum_data,不需要重写数据块。

  3. 读路径优化:读数据时,先从 RocksDB 查 layout 和 CRC,然后直接从裸盘读数据。如果 CRC 在裸盘上,就需要先读数据块头部来获取 CRC,再读数据本身——多了一次 IO。

  4. 数据块对齐:裸盘数据块保持纯净(只有用户数据),不做 read-modify-write 来嵌入元数据头。


八、OMAP(M)—— 对象的 Key-Value 附表

key: "M" + onode_nid + user_key value: user_value

OMAP 是 BlueStore 提供的对象级 key-value 存储接口。它不同于 xattr(xattr 数量少、值小),OMAP 支持海量条目、值可以很大。

典型使用场景

  • RBD(块设备):RBD image 的元数据几乎全部存在 omap 里——image header、snap info、parent reference 等
  • CephFS:目录的文件列表也存在 omap 中(dirname → inode_number映射)
  • RGW(对象网关):bucket index 存在 omap 中

omap 的 key 使用 onode 的nid作为前缀,这样同一个对象的所有 omap 条目在 RocksDB 中是连续排列的,范围扫描效率很高。


九、PG Meta OMAP(P)—— PG 级别的元数据

key: "P" + onode_nid + key value: value

PG(Placement Group)级别的状态信息使用特殊的 omap prefixP存储,与普通对象的 omapM分开。存的是:

  • PG info:PG 的状态、last_update、last_complete 等
  • PG log:PG 的操作日志(用于恢复和一致性检查)

PG meta 对象(如metaosdmap.NNN)是 OSD 内部的"系统对象",不在用户数据空间里。


十、Deferred Transaction(L)—— 延迟写事务

key: "L" + sequence_id value: bluestore_deferred_transaction_t

源码定义(bluestore_types.h第 1005~1021 行):

structbluestore_deferred_transaction_t{uint64_tseq=0;list<bluestore_deferred_op_t>ops;interval_set<uint64_t>released;///< allocations to release after tx};

什么时候会产生 deferred transaction?

BlueStore 的写分两种路径:

  • _do_write_big:大写(对齐到 min_alloc_size),直接在裸盘上分配新空间写入
  • _do_write_small:小写(不对齐的小块覆盖写),需要做 read-modify-write 或 deferred

小块覆盖写无法原地更新(因为 min_alloc_size 的限制),BlueStore 选择把小块写记录为 deferred transaction,先存到 RocksDB,后续异步执行到裸盘上。这样做的好处是:小写不需要立即等裸盘 IO 完成,先记录意图,后续批量执行。


十一、Allocation(B/b)—— 裸盘空间管理

key: "B" + offset (BlockFreelistManager) key: "b" + bitmap_data (BitmapFreelistManager) value: length / bitmap

BlueStore 不依赖宿主机文件系统,自己管理裸盘空间的分配和回收。空闲列表有两种实现:

  • BlockFreelistManager(prefix B):key 是 offset,value 是 length。简单但空间效率低
  • BitmapFreelistManager(prefix b):用 bitmap 紧凑编码,空间效率高,是新版本的默认实现

这个 prefix 记录的是"裸盘上 offset X 处有 length Y 的空闲空间"。当需要为新数据分配空间时,从空闲列表中找到合适的区间;当数据被删除时,释放的区间重新加回空闲列表。

所有分配和释放操作都在 RocksDB transaction 中完成,保证"数据写入 + 空间标记"的原子性。


十二、Shared Blob(X)—— 去重引用计数

key: "X" + sbid (shared blob id) value: bluestore_shared_blob_t

源码定义(bluestore_types.h第 869~892 行):

structbluestore_shared_blob_t{uint64_tsbid;///> shared blob idbluestore_extent_ref_map_t ref_map;///< shared blob extents};

当 Ceph 启用了去重(deduplication)功能时,多个对象可能引用同一块物理数据。此时 Blob 的FLAG_SHARED被设置,物理数据不再属于单个对象,而是被共享。

SharedBlob.ref_map记录每个逻辑偏移有多少对象在引用它——引用计数。当引用计数降为 0 时,物理空间才能被释放。


十三、完整的元数据流转:写一个对象的全过程

假设客户端写一个 4MB 的对象到 OSD,BlueStore 的内部流程如下:

1. 收到写请求 → 创建 TransContext(事务上下文) 2. 分配裸盘空间: - 从 Allocation 空闲列表中找到空闲区间 - 记录分配意图到 RocksDB transaction 3. 写裸盘数据(AIO): - 把 4MB 数据直接写到主数据裸盘的 offset=0x10000000 - 等待 AIO 完成 4. 更新 RocksDB 元数据(同一个 transaction): - PREFIX_OBJ(O):创建/更新 onode,设置 size=4MB - Extent Map:创建 Blob,记录 pextent={0x10000000, 4MB} - Blob.csum:计算每个 chunk 的 CRC,存入 csum_data - PREFIX_ALLOC(B/b):标记已分配空间 - PREFIX_STAT(T):更新统计信息 5. 提交 RocksDB transaction → 写 BlueFS WAL → SST compact → 完成

整个过程的关键保证:步骤 4 中所有 RocksDB 操作在同一个 transaction 中完成。这意味着要么全部成功(onode 更新、layout 更新、CRC 更新、空间分配标记),要么全部失败(回滚)。裸盘上的数据(步骤 3)如果写了但 RocksDB transaction 没提交,那只是"垃圾数据"——因为没有任何元数据指向它,下次空间分配会覆盖它。


十四、读一个对象的过程

1. 从 RocksDB 查 PREFIX_OBJ(O):找到 onode → 得到 size、attrs、extent_map_shards 索引 2. 从 RocksDB 查 Extent Map(inline 或 shard): → 得到每个逻辑偏移对应的 Blob 3. 从 Blob 的 PExtentVector 得到裸盘物理位置: → pextent = {offset=0x10000000, length=4MB} 4. 从裸盘直接 AIO 读数据: → 读 offset 0x10000000,4MB 5. 校验 CRC(可选): → 从 Blob.csum_data 取每个 chunk 的校验值 → 与读到的数据计算值比对

可以看到:读路径非常高效——先查 RocksDB 拿 layout(通常在内存 cache 中),然后一次 AIO 读裸盘。CRC 校验用 RocksDB 里的值,不需要额外读裸盘。


十五、元数据全景总结

Prefix存储内容作用类比
SSuperblock(FSID, UUID, seq)文件系统 superblock
TStat(空间统计)df的底层数据
CCollection(PG 分裂 bits)目录的 inode
OOnode(对象大小、xattr、flags)文件的 inode
O+shardExtent Map → Blob(layout + CRC + 压缩)文件的 extent tree + block map
MOMAP(对象的 KV 附表)文件的 xattr(海量版)
PPG Meta OMAP(PG info/log)文件系统的 journal
LDeferred Transaction(延迟写)文件系统的延迟写日志
B/bAllocation(空闲空间管理)文件系统的 free block bitmap
XShared Blob(去重引用计数)文件系统的 shared block refcount

核心设计原则可以一句话概括:

RocksDB 是 BlueStore 的"大脑"(存所有元数据和决策记录),裸盘是 BlueStore 的"身体"(只存纯粹的用户数据)。大脑和身体通过 Blob 的 PExtentVector 连接——大脑告诉身体"数据应该放在哪个位置",身体只负责存取数据本身。

这种架构的好处是:

  • 原子性:所有元数据变更在 RocksDB 事务中完成
  • 一致性:layout、CRC、空间分配记录始终同步
  • 性能:数据直写裸盘不走 KV 引擎,元数据在 RocksDB 内存缓存中
  • 灵活性:CRC、压缩、去重等特性都可以在元数据层独立演进,不影响数据格式

附录:源码文件索引

文件关键内容
os/bluestore/BlueStore.cc第 70~80 行9 个 KV prefix 定义
os/bluestore/bluestore_types.h第 52~64 行cnode_t(Collection 元数据)
os/bluestore/bluestore_types.h第 70~109 行pextent_t(物理位置)
os/bluestore/bluestore_types.h第 429~862 行blob_t(layout + CRC + 压缩 + 空洞)
os/bluestore/bluestore_types.h第 869~892 行shared_blob_t(去重引用计数)
os/bluestore/bluestore_types.h第 898~970 行onode_t(对象元数据)
os/bluestore/bluestore_types.h第 1005~1021 行deferred_transaction_t
os/bluestore/BlueStore.h第 1038~1090 行内存中的Onode结构
os/bluestore/BlueStore.h第 505 行内存中的Blob结构
os/bluestore/BlueStore.h第 393 行内存中的SharedBlob结构
os/bluestore/BlueStore.cc第 12823 行_do_write_big(大写,直接写裸盘)
os/bluestore/BlueStore.cc第 12462 行_do_write_small(小写,可能 deferred)
os/bluestore/BlueFS.h第 100~103 行BDEV_WAL/DB/SLOW 三级设备定义
http://www.rkmt.cn/news/1424527.html

相关文章:

  • 2026 实时渲染测评:5 款稳定工具推荐,光影全开仍能流畅运行
  • Go语言自然语言处理:文本处理与分析
  • STM32F407标准库实战:串口+DMA收发数据,如何设计一个高效的环形缓冲区管理模块?
  • 你想何出怎样的SRAM CIM
  • 量子视觉场技术:量子计算与计算机视觉的融合创新
  • Python 函数完全指南:定义与调用
  • 网页切图工具,网格切图,非常方便
  • 两个独立事件的联合概率
  • 2026年北京老家具回收机构排行 靠谱之选盘点 - 优质品牌商家
  • 千问大模型在阿里生态中的实战应用指南
  • 收藏!Python小白必看:从零入门大模型,手把手带你掌握企业级实战能力
  • 专访 7 名普通职场人:AI 来了之后,你过得还好吗?
  • 告别风扇噪音与高温:FanControl三分钟搞定Windows散热优化
  • 别再死记硬背Sarsa公式了!用Python手搓一个走迷宫AI,5分钟搞懂On-Policy和Q-learning的区别
  • 工业防爆监控技术解析与山东区域选型实践
  • Windows开始菜单修复终极指南:三步恢复消失的磁贴
  • Codex 新增“宠物”功能:不只是可爱,而是一个轻量工作状态提醒器
  • 工具使用、代理和 Voyager 论文
  • 别再被多重共线性坑了!用Python的sklearn手把手教你调岭回归的alpha参数
  • 2026年嵌丝道口板TOP5厂商盘点 品质与实力对比 - 优质品牌商家
  • 93、CAN FD数据链路层核心:帧结构对比与DLC编码革命
  • 172 号卡哪个推荐码是官方一级?10000 置顶权限真实解析 - 172号卡
  • Lindy自动化项目管理:从概念验证到规模化落地的7个关键决策节点(附20年踩坑清单)
  • 2026年5月更新:浙江老爹鞋制造商业内推荐与趋势解析 - 2026年企业资讯
  • Harness 中的请求影子复制:用于离线分析
  • 我的Obsidian知识库,现在可以自动剪藏笔记到本地了
  • 【从零开始的JUC并发第四章】:JUC常用工具类
  • 新手也能跑通大模型,Hugging Face 环境配置与模型加载指南
  • 5分钟掌握VideoDownloadHelper:你的网页视频下载救星
  • 告别LPC!手把手教你用ESPI协议连接PCH与EC(含信号实测图与模式选择指南)