Linux 内核 Bcache 深度工程:块层写时复制缓存架构与生产调优全解析

一、为什么需要块层缓存

在现代存储系统中,SSD 与 HDD 之间存在几个数量级的性能差距:一块 NVMe SSD 的 4K 随机读可达 800K IOPS,而 HDD 通常不足 200 IOPS。利用 SSD 为 HDD 做缓存,以接近 SSD 的成本获得接近全闪存的混合性能,是企业存储的基本诉求。

Linux 内核生态中有三种主要的块层缓存方案:

方案状态缓存粒度写策略元数据开销
Bcache内核主线(5.4+)512B sectorwriteback / writethrough / writearound每 cached_dev 约 1-2MB B+Tree 根
dm-cache内核主线(3.9+)64KB-1MB 块writeback / writethroughpolicy 依赖元数据
Flashcache第三方内核模块4KB pagewriteback / 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 加速方案借鉴。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部