Linux 内核 Bcache 深度工程:块层写时复制缓存架构与生产调优全解析
一、为什么需要块层缓存
在现代存储系统中,SSD 与 HDD 之间存在几个数量级的性能差距:一块 NVMe SSD 的 4K 随机读可达 800K IOPS,而 HDD 通常不足 200 IOPS。利用 SSD 为 HDD 做缓存,以接近 SSD 的成本获得接近全闪存的混合性能,是企业存储的基本诉求。
Linux 内核生态中有三种主要的块层缓存方案:
| 方案 | 状态 | 缓存粒度 | 写策略 | 元数据开销 |
|---|---|---|---|---|
| Bcache | 内核主线(5.4+) | 512B sector | writeback / writethrough / writearound | 每 cached_dev 约 1-2MB B+Tree 根 |
| dm-cache | 内核主线(3.9+) | 64KB-1MB 块 | writeback / writethrough | policy 依赖元数据 |
| Flashcache | 第三方内核模块 | 4KB page | writeback / writearound | 全局哈希表套桶 |
Bcache 的独特优势在于:它工作在块设备层而不是 dm 层,因此无需修改存储栈即可叠加;它的元数据嵌入缓存设备自身,不占用宝贵的 SSD 容量做额外索引;它的写回模式使用 B+Tree 索引 SSD 上的脏数据段,写放大可控。
二、Bcache 架构总览
Bcache 在系统栈中的位置如下:
┌─────────────────────────────────────────┐
│ File System (ext4 / xfs / btrfs) │
├─────────────────────────────────────────┤
│ Block Layer (blk-mq) │
├─────────────────────────────────────────┤
│ Bcache Virtual Device (/dev/bcache0) │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │ B+Tree │───>│ Cache Device (SSD) │ │
│ │ (索引) │ │ ┌ ─ ─ ─ ─ ┐ │ │
│ └─────────┘ │ │Bucket │ │ │
│ │ │ │ Dirty │ │ │
│ │ │ │ Clean │ │ │
│ │ │ │ Unused │ │ │
│ │ │ └ ─ ─ ─ ─ ┘ │ │
│ │ └─────────────────────┘ │
│ │ 写回 / 透写 │
│ v │
│ ┌──────────────────────────────────┐ │
│ │ Backing Device (HDD) /dev/sda │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
核心数据结构:
struct bcache_device /* 虚拟块设备 */
struct cached_dev /* 缓存设备(SSD)状态 */
struct bcache /* 后端设备(HDD)的 bcache 上下文 */
struct bkey /* B+Tree 键:指向 (inode, offset, ptr) */
struct btree /* 每棵 B+Tree,管理一个 backing 设备的 key 空间 */
struct bucket /* 缓存设备的最小分配单元(通常 512KB-1MB) */
struct cache /* 单个缓存设备的元数据和桶管理 */
struct cache_set /* 多个缓存设备的聚合(副本集) */
三、B+Tree 索引 — 缓存数据的核心
Bcache 使用 B+Tree 将 backing 设备上的逻辑扇区映射到缓存设备上的物理指针。每个 BTree node 默认 512 个 B+Tree 槽位(256 个 key),node 内数据排序以支持二分查找。
键的结构:
union {
struct {
u64 inode; // 后端设备在 cache_set 中的唯一 ID
u64 offset; // 扇区偏移
u16 keys; // 覆盖的 512B 扇区数
u8 ptr[]; // 缓存设备物理位置指针数组
} packed;
};
指针 (pointer) 包含目标缓存设备的 ID、bucket ID 和区间内偏移量(sector 级)。这种 indirection 设计使得 rebalance(GC 或写回触发将数据移入连续空间)无需更新上层引用。
关键操作路径:
IO 命中 → bch_lookup() 在 B+Tree 二分查找
→ 找到 → bio 重定向到 cache 设备
→ 未命中:
writethrough → 直接透传到 backing
writearound → 写入 backing,完成后再决定是否缓存
writeback → 插入空 key,分配 bucket,写入 SSD,异步刷回 HDD
四、写策略的语义与工程取舍
4.1 writethrough(默认)
每个 IO 同时写入 backing 和 cache,只有两边都完成才返回 ACK。优点是无断电丢数据风险;缺点是 SSD 无法覆盖 HDD 的写放大,写延迟等于 max(SSD, HDD)。适合数据库类对数据一致性要求极高的负载。
4.2 writearound
写 IO 直接 bypass cache 到 backing;仅当检测到顺序或热点读时,Bcache 才将数据从 backing 拷贝到 cache。优点是 SSD 写磨损最小;缺点是突发 write burst 会完全绕过缓存,SSD 投资回报率降低。
4.3 writeback(高性能)
写 IO 仅写入 cache,异步刷回 backing。这是 Bcache 性能最强的模式,也是部署中风险最高的模式。Bcache 使用 writeback_dirty_bkey_fn 将超阈值的脏 bucket 批量顺序写回 HDD:
// 核心写回门槛(/sys/fs/bcache/.../)
dirty_data_threshold // 脏数据总量触发写回的比例(默认 10%)
writeback_delay // key 变脏后最少保留 N 秒再允许写回
writeback_rate // 限流:每秒最多写回 MB 数据
writeback_percent // 已脏 bucket 占比达此值开始新 key 写回
// 实际内核逻辑:100Hz 下,btree_garbage_grow() 每 tick 检查
if (dirty_buckets > threshold) wake_up_process(writeback_thread)
writeback_page() 遍历 B+Tree 脏区段 → 发 bio 到 HDD → 完成后置 clean
工程要点:writeback 模式必须保证 cache 设备有掉电保护(PLP),否则脏数据丢失。消费级 SSD 的 PLP 能力参差不齐,企业 NVMe/Optane 较可信。
五、垃圾回收(GC)与写放大
Bucket 是 GC 的基本单元。当一个 bucket 中的无效 sector 比例超过阈值时,Bch 触发 GC:将有效数据读出压缩后写入新 bucket 回收整个 bucket。
// 触发条件(/sys/fs/bcache/.../cache%d/)
inconsistent_p; // GC 比例阈值(默认 50%)
// GC 核心函数 bch_gc_thread()
loop:
1. 找到无效比例最高的桶
2. bch_gc_start() 读取桶内有效 key
3. 写入新分配的桶
4. 更新 B+Tree 指向新指针
5. 原桶标记为可回收
写放大因子(WAF)是衡量 GC 效率的关键指标。Bcache 默认的 SSD allocator 从前向后搜索空闲桶,在持续工作负载下会产生较多碎片。使用内核参数 cache_replacement_policy=lru 可以将桶以 LRU 形式管理,减少无效页面的跨桶分布。
六、缓存命中率与热数据识别
Bcache 内置两个维度的命中率统计:
cat /sys/fs/bcache/UUID/stats_total/cache_hits
cat /sys/fs/bcache/UUID/stats_total/cache_misses
cat /sys/fs/bcache/UUID/stats_total/cache_bypass_hits
// 计算命中率
hits=$(cat stats_total/cache_hits)
miss=$(cat stats_total/cache_misses)
hitrate=$(echo "scale=2; $hits * 100 / ($hits + $miss)" | bc)
命中率低于 70% 时,Bcache 默认自动将 writeback 切至 writethrough,避免维持脏数据的成本超过缓存收益。这是 writeback_hit_percent 和 writeback_collapse 统计项驱动的自动决策。
热数据识别依赖读:被超过一次的重复读 512B 区域会被 B+Tree 挂入 "readdalready" 标记。使用 sequential_cutoff(默认 4MB)配置:超过此大小的连续 IO 会被视为顺序流,直接穿透缓存不污染热点列表。这对备份和虚拟化镜像读写非常关键。
七、生产配置实战
7.1 基础创建(make-bcache)
# 安装工具
apt install bcache-tools # 或 yum install bcache-tools
# 创建缓存设备(SSD)
make-bcache -C /dev/sdb # SSD
# 创建后端设备(HDD)
make-bcache -B /dev/sda # HDD
# 注册并绑定
echo "CACHE-UUID" > /sys/fs/bcache/register
echo "BACKING-UUID" > /sys/fs/bcache/register
echo "BACKING-UUID" > /sys/block/bcache0/bcache/attach
7.2 关键调优参数
# 1. 切到 writeback 模式
echo writeback > /sys/block/bcache0/bcache/cache_mode
# 2. 写回限流:保持 HDD 不被 IO 打满
echo 16M > /sys/block/bcache0/bcache/writeback_rate # 16MB/s
# 3. 脏数据水量控制
echo 20 > /sys/block/bcache0/bcache/dirty_data_threshold # 改为 20%
# 4. 顺序 IO 穿透避免缓存污染
echo 8M > /sys/block/bcache0/bcache/sequential_cutoff # 8MB 以上顺序 IO 绕过
# 5. 关闭自动 writethrough 回退
echo 0 > /sys/block/bcache0/bcache/writeback_running # 强制保持 writeback
echo 1 > /sys/block/bcache0/bcache/writeback_percent
7.3 NVMe 后端 + HDD 混合场景
在 NVMe 为 cache、HDD 为 backing 的场景下(典型云存储机型),瓶颈一是 NVMe PLP 可靠性,二是 HDD 写回吞吐。推荐配置:
PRIORITY:
1. cache_replace_policy=lru // 桶 LRU
2. writeback_delay=1 // 1 秒脏数据保持,合并小写
3. writeback_percent=75 // 桶 75% 脏时触发刷盘
4. sequential_cutoff=8M // 8MB 以上顺序 IO 不缓存
5. writeback_running=1 // 始终允许写回
八、性能基准与监控
实测案例:单盘 HDD(180 MB/s seq)+ NVMe SSD 做 writeback 缓存,使用 fio:
# 4K 随机读(Q8T1,模拟小文件随机读写)
fio --name=randread --ioengine=libaio --direct=1 \
--rw=randread --bs=4k --numjobs=8 --iodepth=32 \
--size=10G --runtime=60
// 不会有 bcache 的 HDD:~200 IOPS
// 有 writeback bcache:~18000 IOPS(约 90x 提升)
# 写混合(70%读 / 30%写)
fio --rw=randrw --rwmixread=70 --bs=4k --iodepth=32
// writeback 模式:写延迟 < 1ms,远超 HDD 原生
监控指标(建议接入 Prometheus + Grafana):
node_bcache_cache_hits_total
node_bcache_cache_misses_total
node_bcache_bypasses_total
node_bcache_cache_hit_ratio // 实时命中率
node_bcache_writeback_rate // 当前写回速率
node_bcache_dirty_data // 脏数据量
九、故障与恢复
Bcache 对缓存设备故障 (SSD 损坏) 的处理策略:
- writethrough 模式:SSD 坏了可立即 detach cache_set,backing 数据完全有效,零数据丢失。
- writeback 模式:SSD 坏了会丢失所有未刷回的脏数据。Bcache 不提供多副本缓存,需要靠外部冗余(RAID1 SSD 或 Optane PLP)来规避。
恢复命令:
# 检测 SSD 丢失后,停止 writeback
echo 0 > /sys/block/bcache0/bcache/writeback_running
# detach 失效缓存
echo 1 > /sys/block/bcache0/bcache/detach
# 换新 SSD 后重新 attach
echo NEW-CACHE-UUID > /sys/block/bcache0/bcache/attach
十、总结
Bcache 是 Linux 块层少有的"教科书级"解决方案:它利用 B+Tree 实现地址映射,用 bucket 分配器和异步写回线程把随机写化顺序写,用 GC 与 LRU 策略控制写放大。虽然 dm-cache 在元数据灵活性上更强,但 Bcache 的零额外元数据和原生内核集成优势使其成为大多数 HDD+SSD 混合存储的首选方案。
工程落地的关键三要素:
- SSD 必须有掉电保护,否则 writeback 模式不可用
- 必须设置 sequential_cutoff 避免缓存污染
- 写回速率必须匹配 backing 设备的物理吞吐
在 CXL 内存分层和 ZNS SSD 兴起的未来,Bcache 的架构思想(块层加缓存设备 + 异步写回)仍会持续演进,而它作为第一个成熟的通用块层缓存,其设计语言一直被后续的 IO 加速方案借鉴。

发表评论 取消回复