写路径(第 8 篇)解决了「数据如何落盘」;读路径要回答的是:如何从 onode → extent_map → blob → 裸盘字节拼回正确内容,并在静默损坏时尽早失败。BlueStore 必须同时读 RocksDB(元数据)与 BlockDevice(数据),再用多层缓存挡住重复代价。本篇按调用链展开,并把 checksum 失败与 2Q 抗扫描污染写成可证伪命题,而不是「读比写简单」的印象句。
本文是「Ceph / RADOS 存储内核」系列第 9 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 8 篇 · BlueStore 写路径 min_alloc_size、deferred/WAL、checksum、压缩 第 9 篇 · BlueStore 读路径 onode、blob、缓存架构、checksum 失败模式 第 10 篇 · Recovery / Backfill / Scrub reservation、async recovery、mClock 边界 上一篇:BlueStore 写路径 · 下一篇:Recovery / Backfill / Scrub
版本锚定:Ceph Squid v19.2.5;
docs.ceph.com/en/squid/rados/configuration/bluestore-config-ref/;源码src/os/bluestore/BlueStore.cc,函数BlueStore::_do_read()@ v19.2.5。
一、读路径总览
一次 RADOS 读操作进入 BlueStore 后,经历以下几个阶段:
flowchart LR
A["read(oid, offset, length)"] --> B["onode 查找\n(cache → RocksDB)"]
B --> C["extent_map 展开\n→ blob 列表"]
C --> D["blob 解引用\n→ pextent 列表"]
D --> E["BlockDevice aio_read\n→ bufferlist"]
E --> F{压缩?}
F -->|是| G["解压缩"]
F -->|否| H["checksum 验证"]
G --> H
H --> I{校验通过?}
I -->|通过| J["返回数据"]
I -->|失败| K["EIO + 报错日志"]
整个过程的性能关键点在于两个缓存命中:onode
缓存(避免 RocksDB
读取)和数据缓存(避免 aio
读取)。未命中时,分别触发 RocksDB Get 和 BlockDevice
aio_read。排障时先分清「慢在元数据面还是数据面」,再决定是调
bluestore_cache_meta_ratio、rocksdb
参数,还是盘与 scrub
窗口——混为一谈会调错旋钮。这一分法贯穿本篇后续各节。
二、onode 查找
2.1 onode 缓存
BlueStore 在内存中维护 onode
缓存(BlueStore::OnodeCache / 相关
Cache 分片),按 collection(PG)分片。查找顺序:
- 由 collection +
ghobject_t构造与PREFIX_OBJ一致的查找键; - 分片 LRU(或当前实现所用策略)命中 →
OnodeRef; - 未命中 → RocksDB
Get,反序列化bluestore_onode_t,插入缓存。
容量由有效 bluestore_cache_size*
与比例瓜分。Squid config-ref 给出数据缓存份额公式:
[ = (1 - - ) ]
onode/meta 与 RocksDB block cache(kv)各吃掉一部分;开启
bluestore_cache_autotune 时还会向
osd_memory_target 靠拢(best-effort,不低于
osd_memory_cache_min)。调比例前先确认 autotune
是否在改你的「手调」假设。
2.2 RocksDB 读路径
未命中时:MemTable → RocksDB block cache → BlueFS 上的 SST。两级缓存分工:
- Onode cache:反序列化后的 C++ 对象,避免反复 decode;
- RocksDB block cache:SST block,避免反复读 BlueFS。
omap 大的工作负载(如 RGW bucket index)压力在
PREFIX_OMAP(M/m/p…),与
onode 前缀不同;compaction 抖动会同时抬高「找 onode」和「读
omap」的尾延迟。
三、extent_map 展开与 blob 解引用
读路径在拿到 onode 之后,不直接「按文件偏移读盘」,而是:
逻辑 [offset, offset+length)
→ ExtentMap 区间查询
→ 每个 Extent:blob + blob_offset
→ bluestore_blob_t::extents(pextent 列表)
→ 一次或多次 BlockDevice::aio_read
→ (可选)解压 → checksum → 裁剪拼接
3.1 extent_map
ExtentMap 是逻辑偏移上的区间映射(实现为
interval_map 一类结构)。每个
Extent 含:
logical_offset、length;- 指向
Blob的引用; blob_offset:逻辑区间在该 blob 内的起点。
跨多个 extent 的读会拆成多个 blob
读;_do_read() 可合并相邻物理
I/O,但无法合并真正分散的
pextent——长期部分覆盖会造成对象碎片化,HDD
上表现为单次逻辑读触发多次随机 aio(第七节)。
3.2 blob → pextent
bluestore_blob_t 的 extents
是裸盘 (disk_offset, disk_length)
列表;分配时找不到连续大块就会打散。压缩 blob 的 pextent
描述的是压缩后字节;shared blob 的引用在
SharedBlob /
PREFIX_SHARED_BLOB(X),读不改变引用计数。
若写路径刚提交的 deferred
尚未应用到裸盘,读路径仍须返回与 onode 一致的逻辑数据(与第
8 篇 deferred 不变量衔接);实现细节以 v19.2.5
_do_read() 为准,排障时不要假设「看盘偏移 =
看逻辑内容」。
四、数据缓存:2Q 与扫描污染
BlueStore
数据缓存(BufferCache /
BufferSpace)缓存从裸盘读出、供上层使用的数据块;淘汰算法是
2Q,而 onode 侧常用
LRU。这不是审美差异,而是针对「前台热点 +
后台全量扫描」混合负载。
4.1 算法锚点(Johnson & Shasha, VLDB 1994)
2Q(《2Q: A Low Overhead High Performance Buffer Management Replacement Algorithm》,Proceedings of the 20th VLDB Conference, 1994)把缓冲分成:
- A1in:首次见到的页,FIFO 冷区;
- A1out:从 A1in 淘汰后留下的 ghost 标识(只记身份,不占数据页),用于识别「短时间再次访问」;
- Am:第二次(经 A1out 证明)访问后升入的热区,近似 LRU。
论文动机(§1–2):经典 LRU 在顺序扫描时会被一次性触碰的冷页淹没,把热点挤出——即 scan pollution。2Q 用「只进过一次 A1 的页」与「复访页」分离,使扫描流量主要伤害冷区。
4.2 为何对准 BlueStore
Ceph OSD 上 deep scrub、backfill、recovery
会顺序摸大量对象;若数据缓存用纯
LRU,前台热对象的缓存命中率会在 scrub 窗口塌陷。2Q
把首次顺序触碰留在冷区,降低该类污染——这是机制对齐:Johnson
& Shasha 的假设(存在扫描型访问)在 OSD 上可检验(对比
scrub 时段 cache_hit 类计数是否相对 LRU
假想更稳;本站未做替换算法 A/B,不编造命中率百分点)。
数据区容量见上文公式中的
data_fraction。典型数量级随介质与
osd_memory_target 变化,以
ceph config get osd bluestore_cache_size /
autotune 实际为准。
4.3 缓存 miss
miss →
BlockDevice::aio_read()。延迟量级随介质变化(HDD
毫秒级随机读、NVMe
微秒级),此处只作数量级提示,不是基准测试结论。观测:ceph tell osd.N perf dump
中与 bluestore cache 相关的 hit/miss 计数。
五、Checksum 失败模式
数据从裸盘读出后,按 blob 记录的算法与
csum_chunk_order 分块计算,与
csum_data
比对。压缩路径:先对压缩字节验
csum,再解压(第 8 篇)。
5.1 正常路径
对每个块大小 (s = 2^{}):
\[\text{csum\_computed}[i] = H\big(\text{data}[i \cdot s \,:\, (i+1) \cdot s]\big)\]
其中 (H) 为 blob 上记录的算法(如 crc32c)。任一 (i) 不等即失败。
5.2 失败后的分层处理
| 层 | 行为 | 可观测 |
|---|---|---|
| BlueStore | _verify_csum() 失败 → 向上返回
EIO;打错误日志(含对象、偏移、期望/实际
csum) |
OSD log |
| PrimaryLogPG(副本池) | 尝试从其他 acting OSD 读;成功则服务读并走修复/标记损坏对象 | ceph health:damaged objects |
| ECBackend(EC 池) | 用其余分片重建(第 11 篇);写回坏 shard | PG inconsistent / repair |
| 客户端 | 若冗余不足以修复 | 读失败 EIO |
不可做的假设:checksum 失败 ≠
自动选「最新版本」覆盖全集群;deep scrub
发现不一致后,官方路径常需显式
ceph pg repair,以避免把坏副本扩散(第 10
篇)。
5.3 检测边界(可证伪命题)
- 命题 A:从未被读、且未轮到 deep scrub 的对象,即使盘上静默翻转,客户端与 health 也可暂时无感。→ 真;检测依赖读或 scrub。
- 命题 B:开启
csum_type=none时,BlueStore 读路径仍能发现比特翻转。→ 假。 - 命题 C:shallow scrub 等于 deep scrub 的数据面检测。→ 假;shallow 主要比元数据。
默认 osd_deep_scrub_interval 约 7
天:冷数据的发现窗口上界与此同量级(具体以集群配置为准)。
六、压缩数据的读路径
blob 标记了 FLAG_COMPRESSED
时,从裸盘读出的是压缩数据,需要先解压再验证(注意:checksum
验证的是压缩数据,即读出→验
checksum→解压,而不是读出→解压→验 checksum)。
解压完成后,数据缓存存储的是解压后的数据(而非压缩数据),这样第二次读同一 blob 时不需要重复解压。这是一个工程取舍:缓存解压数据消耗更多内存,但减少 CPU 开销;对于 CPU 紧缺的 HDD OSD 场景,缓存解压数据更合理;对于内存紧缺的高密度 OSD,反而希望存压缩数据节省内存。Squid 中 BlueStore 的数据缓存默认存解压后数据,此行为以 v19.2.5 源码为准。
七、读性能的瓶颈分析
读延迟按机制优先级大致如下(均为因果链,不是排名榜):
- onode 缓存命中率:未命中则 RocksDB Get;compaction 忙碌时尾部拉长。
- 数据缓存命中率:未命中则 aio_read;介质决定数量级。
- RocksDB compaction 干扰:SST 合并与 onode/omap 读争 BlueFS 带宽——与第 8 篇写路径是同一控制面缺口在读侧的投影。
- extent 碎片化:一次逻辑读拆成多次随机
aio;
_do_read()只能合并相邻请求。
可检验做法:在 compaction 计数上升窗口与 deep scrub 窗口分别盯客户端读 P99,看哪一个窗口更「像」元数据面瓶颈、哪一个更像数据面带宽瓶颈。不要把两种毛刺用同一句「OSD 慢了」糊过去。
八、争论:BlueStore 缓存 vs 内核页缓存
FileStore 依赖内核页缓存;BlueStore 绕过 VFS,只用自管 onode cache + 2Q 数据 cache。争论可以压成可检验命题:
- 自管派:淘汰策略与 scrub/recovery 扫描可预期;不与主机其他进程抢 page cache;DIRECT I/O 语义清晰。证据:生产默认路径与 config-ref 的 cache 比例模型。
- 页缓存派:内核 readahead 对顺序读更省心;BlueStore 必须自研预读(recovery/backfill 批量 aio),否则顺序修复偏慢。证据:运维上对「修复期读放大」的体感,而非单一公开 benchmark。
主线选择是强化 BlueStore
侧预读与缓存,而不是退回页缓存。Crimson/Seastore
把该分叉推得更远:I/O 完全在 Seastar
框架内,不依赖内核缓存层(第 18
篇)。对排障而言,更有用的检验是:scrub 窗口内
bluestore cache hit
是否塌陷、以及塌陷是否与客户端读 P99 同向——这比「该不该用
page cache」口号更接近生产。
九、开放问题
1. RocksDB compaction 与读路径 P99
的相关性。在小 HDD 集群中,RocksDB compaction
产生的大量顺序读(读 SSTable)与 onode Get(也是 RocksDB
读)竞争 BlueFS 的 I/O 通道,导致 onode 查找延迟的 P99
突然上升,进而传导到客户端读 P99。这个传导链路目前没有单独的
perf counter 反映,只能通过 histograms 和
ceph tell osd.N perf dump
的组合推断。问题的可检验形式:rocksdb_num_files_at_level*
指标持续增长期间客户端读 P99 是否同步上升。
2. 静默数据损坏(silent data corruption)与 deep
scrub 的时间窗口。默认 7 天的 deep scrub
周期意味着一块全新写入但从未被读取的冷数据,理论上可以在损坏
7 天内未被发现。在单副本 EC 场景(k+2 等 EC
配置)中,如果主分片损坏而备用分片也损坏(两盘同时静默故障),scrub
前没有任何机制感知。问题的解法方向:增加 CRC 校验频率(减小
osd_deep_scrub_interval,但增加 I/O
开销),或引入后台数据验证守护线程(尚无主线实现)。
3.
压缩数据缓存的内存效率。数据缓存默认存解压后字节时,高压缩比对象会按逻辑大小吃内存,而不是按盘上物理占用。内存紧的高密度
OSD
上,这会表现为「缓存配得不小,却盖不住热工作集」。可检验:对比压缩池与未压缩池在相同
bluestore_cache_size 下的 hit 率与
RSS;若压缩池 hit 率尚可但 RSS 已顶住
osd_memory_target,说明缓存住的是放大后的逻辑视图。是否改为缓存压缩字节,属于实现策略取舍,Squid
默认行为以源码为准;调优前先确认这一点,避免按物理占用去估算缓存覆盖率。
十、参考资料
规范 / 官方文档(A 级)
- Ceph Squid,BlueStore Configuration
Reference(
bluestore_cache_size、bluestore_cache_meta_ratio、bluestore_csum_type等),docs.ceph.com/en/squid/rados/configuration/bluestore-config-ref/。 - Ceph Squid,BlueStore
Internals,
docs.ceph.com/en/squid/dev/bluestore/。
源码(A 级)
ceph/cephtag v19.2.5:src/os/bluestore/BlueStore.cc(_do_read()、_verify_csum()、BufferCache、OnodeCache);src/os/bluestore/bluestore_types.h(bluestore_blob_t::csum_data)。
论文(A 级)
- Aghayev et al. (2019). File Systems Unfit as Distributed Storage Backends. SOSP ’19. Section 5:BlueStore 读路径性能分析与缓存设计动机。
- Johnson, T., & Shasha, D. (1994). 2Q: A Low Overhead High Performance Buffer Management Replacement Algorithm. Proceedings of the 20th International Conference on Very Large Data Bases (VLDB ’94). BlueStore 数据缓存对抗 scan pollution 的算法锚点;与 scrub/recovery 顺序扫描负载对齐。
站内
- 第 8 篇:BlueStore 写路径 · 第 10 篇:Recovery / Backfill / Scrub · 系列目录
- RocksDB 内核系列(RocksDB Block Cache 机制)
→ 系列目录 · 上一篇:BlueStore 写路径 · 下一篇:Recovery / Backfill / Scrub
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Ceph RADOS】BlueStore 写路径:min_alloc_size、deferred/WAL、checksum 与压缩
以 min_alloc_size 为分叉点拆解 BlueStore 的两条写路径:小写经 deferred WAL 先落 RocksDB 再后台应用到裸盘,大写直接落裸盘后原子更新 onode;说明 checksum 计算时机和压缩对分配逻辑的影响。
【Ceph RADOS】RADOS 内核全景:五轴坐标系与 18 篇路线
补齐站内 distributed/38 之上的 RADOS 内核层缺口;定义 Map 传播、通信、一致性、本地存储、接入代价五条坐标系,给出 18 篇阅读路线与系列边界。
【Ceph RADOS】BlueStore 架构:BlockDevice、BlueFS、RocksDB 与 Allocator
拆解 BlueStore 四个核心组件的职责边界:BlockDevice 直控裸盘、BlueFS 为 RocksDB 提供最小文件系统、RocksDB 管理对象元数据、Allocator 追踪空闲空间;交代取代 FileStore 的工程动机与 SOSP'19 学术锚点。
【Ceph RADOS】排障坐标系:slow ops、peering 卡死、满盘、恢复拖死前台与 checksum 错误
按五轴映射 Ceph 常见故障症状:slow ops(慢操作)、peering 卡死、满盘/nearfull、recovery 拖死前台、checksum/inconsistent——每轴给出核对顺序与观测入口,不粘贴伪造集群输出。