Bcachefs 完全指南 / 第 3 章:架构与设计原理
第 3 章:架构与设计原理
理解底层设计,才能善用高层特性
3.1 整体架构概览
Bcachefs 的架构可以用一句话概括:一个基于 B-Tree of B-Trees 的 CoW 文件系统。
┌─────────────────────────────────────────────────────────┐
│ 用户空间 (User Space) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ 应用程序 │ │ bcachefs │ │ mount / bcachefs │ │
│ │ │ │ -tools │ │ format / bcachefs │ │
│ └──────────┘ └──────────┘ │ fsck │ │
│ └──────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 内核空间 (Kernel Space) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ VFS (虚拟文件系统) │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴─────────────────────────┐ │
│ │ Bcachefs 文件系统驱动 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────────────┐ │ │
│ │ │ B-Tree │ │ CoW │ │ Journal 系统 │ │ │
│ │ │ 引擎 │ │ 引擎 │ │ │ │ │
│ │ └─────────┘ └─────────┘ └──────────────────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────────────┐ │ │
│ │ │ 压缩 │ │ 校验和 │ │ 快照/子卷 │ │ │
│ │ └─────────┘ └─────────┘ └──────────────────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────────────┐ │ │
│ │ │ 去重 │ │ 加密 │ │ 多设备/RAID │ │ │
│ │ └─────────┘ └─────────┘ └──────────────────┘ │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴─────────────────────────┐ │
│ │ Block Layer (块设备层) │ │
│ └───────────────────────┬─────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴─────────────────────────┐ │
│ │ 存储设备 (SSD / HDD / NVMe) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
3.2 B-Tree 结构
3.2.1 什么是 B-Tree
B-Tree(平衡多路搜索树)是数据库和文件系统中使用最广泛的索引结构。Bcachefs 使用的是 B+Tree 的变体。
传统 B-Tree:
[30 | 60]
/ | \
[10|20] [40|50] [70|80]
/ | \ / | \ / | \
叶子节点 叶子节点 叶子节点
Bcachefs B-Tree:
每个节点存储 key-value 对
key = (inode, offset, size) 三元组
value = 数据指针 / 元数据
3.2.2 Bcachefs 的 B-Tree 设计
Bcachefs 使用 “B-Tree of B-Trees” 分层结构:
┌─────────────────────────────────────────────────┐
│ Superblock (超级块) │
│ 存储文件系统元信息、设备列表、配置 │
└──────────────────────┬──────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───────┴───────┐ ┌────┴─────┐ ┌─────┴─────┐
│ Inodes B-Tree │ │ Extents │ │ Dirents │
│ (索引节点) │ │ B-Tree │ │ B-Tree │
│ │ │ (数据) │ │ (目录) │
└───────────────┘ └──────────┘ └───────────┘
│ │ │
┌───────┴───────┐ ┌────┴─────┐ ┌─────┴─────┐
│ 其他 B-Tree │ │ 校验和 │ │ 扩展属性 │
│ - Snapshots │ │ B-Tree │ │ B-Tree │
│ - Subvolumes │ │ │ │ │
│ - Xattrs │ │ │ │ │
└───────────────┘ └──────────┘ └───────────┘
3.2.3 Key-Value 编码
Bcachefs 的 B-Tree 节点存储统一格式的 key-value 对:
Key 格式:
┌──────────┬──────────┬──────────┬──────────┐
│ inode │ offset │ size │ type │
│ (64 bit) │ (64 bit) │ (32 bit) │ (8 bit) │
└──────────┴──────────┴──────────┴──────────┘
Key 类型:
- BKEY_TYPE_INODES → inode 元数据
- BKEY_TYPE_EXTENTS → 数据块指针
- BKEY_TYPE_DIRENTS → 目录项
- BKEY_TYPE_XATTRS → 扩展属性
- BKEY_TYPE_ALLOC → 分配信息
- BKEY_TYPE_SNAPSHOT → 快照信息
- BKEY_TYPE_SUBVOLUME → 子卷信息
3.2.4 B-Tree 操作复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 查找 | O(log N) | B-Tree 高度很低,通常 3-4 层 |
| 插入 | O(log N) | 可能触发节点分裂 |
| 删除 | O(log N) | 可能触发节点合并 |
| 范围扫描 | O(log N + K) | 顺序遍历叶子节点 |
3.3 Copy-on-Write (CoW) 机制
3.3.1 CoW 基本原理
传统文件系统写入:
1. 读取旧数据块 A
2. 修改 A 的内容
3. 写回 A(原地覆盖)
4. 如果步骤 3 失败 → 数据损坏
Bcachefs CoW 写入:
1. 分配新数据块 A'
2. 将新数据写入 A'
3. 原子更新 B-Tree 指针: A → A'
4. 如果步骤 3 失败 → A 仍然完整,A' 被回收
3.3.2 CoW 写入流程
写入文件 "data.txt" 的数据:
步骤 1: 读取 B-Tree,找到数据块位置
┌──────────────────────────────────┐
│ Inode: 1234, Offset: 0, → Block A │
└──────────────────────────────────┘
步骤 2: 分配新块 A',写入新数据
┌──────────────────────────────────┐
│ 新分配: Block A' (写入新数据) │
└──────────────────────────────────┘
步骤 3: 创建新的 B-Tree 节点
┌──────────────────────────────────┐
│ Inode: 1234, Offset: 0, → Block A'│
└──────────────────────────────────┘
步骤 4: 原子更新父节点指针
旧: parent → [A, B, C]
新: parent → [A', B, C] ← 原子操作
步骤 5: 旧块 A 标记为可回收
3.3.3 CoW 的优势与代价
| 方面 | 优势 | 代价 |
|---|---|---|
| 数据安全 | 崩溃后一致,无需 fsck | - |
| 快照 | O(1) 创建,共享旧数据 | 删除快照时需要遍历 |
| 并发 | 读不阻塞写 | 写-写需要锁协调 |
| SSD 友好 | 顺序写入 | 写放大 (Write Amplification) |
| 碎片化 | - | 长期使用可能碎片化 |
3.3.4 写放大问题
写放大 (Write Amplification):
ext4 修改 4KB:
写入 4KB → 磁盘写入 4KB
放大比: 1x
bcachefs 修改 4KB (CoW):
写入新 4KB 数据块 + 更新 B-Tree 节点 + 更新 Journal
实际磁盘写入: ~12-16KB
放大比: 3-4x
缓解措施:
1. 延迟写回 (Write-back) — 合并小写入
2. 批量提交 B-Tree 节点
3. 日志压缩
4. SSD 的 TRIM/UNMAP 支持
3.4 Journal (日志) 系统
3.4.1 日志的作用
无 Journal 的文件系统:
写入步骤 → 如果在步骤 2/3 之间崩溃
→ 元数据不一致 → 需要 fsck 扫描全盘
有 Journal 的文件系统:
1. 先写 Journal(日志)
2. 再写实际数据
3. 标记 Journal 完成
如果在步骤 1/2 之间崩溃 → 回放 Journal 恢复
3.4.2 Bcachefs 的 Journal 设计
Journal 结构:
┌─────────────────────────────────────────────────┐
│ Journal Sequence Number (递增序列号) │
├─────────────────────────────────────────────────┤
│ Entry 1: B-Tree 操作日志 │
│ Entry 2: 分配器变更 │
│ Entry 3: 引用计数变更 │
│ ... │
├─────────────────────────────────────────────────┤
│ Journal 校验和 (保证日志本身完整性) │
└─────────────────────────────────────────────────┘
Journal 设备:
- 可以使用专用的快速设备(如 NVMe)存储日志
- 默认使用主设备
- 专用日志设备可以减少写放大
3.4.3 Journal 回放
# 崩溃后的恢复流程:
# 1. 挂载时自动检查 Journal
# 2. 查找最近的完整 Journal 条目
# 3. 回放未完成的操作
# 4. 文件系统恢复到一致性状态
# 手动检查 Journal 状态
sudo bcachefs show-super /dev/sdX
# 查看 journal_seq 字段
3.5 存储分配器
3.5.1 Bucket 分配模型
Bcachefs 使用 Bucket(桶) 作为基本分配单元:
设备分区:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ Bucket 0│ Bucket 1│ Bucket 2│ Bucket 3│ ... │
│ 512 KB │ 512 KB │ 512 KB │ 512 KB │ │
└─────────┴─────────┴─────────┴─────────┴─────────┘
每个 Bucket 的状态:
- FREE (空闲)
- DIRTY (有脏数据)
- CLEAN (有干净数据,可回收)
- META (存储元数据)
- NEEDS_GC (需要垃圾回收)
3.5.2 Bucket 大小选择
# 创建文件系统时指定 bucket 大小
sudo bcachefs format /dev/sdX --bucket_size=256K
# Bucket 大小的选择建议:
# 小 bucket (64K-256K):
# - 适合小文件多的场景
# - 减少空间浪费
# - 元数据开销更大
#
# 大 bucket (512K-2M):
# - 适合大文件场景
# - 元数据开销小
# - 可能浪费空间(小文件不满一个 bucket)
# 默认 bucket 大小根据设备大小自动选择:
# 设备 < 50GB → 64KB
# 设备 < 500GB → 128KB
# 设备 < 5TB → 256KB
# 设备 < 50TB → 512KB
# 设备 ≥ 50TB → 1MB
3.5.3 垃圾回收 (Garbage Collection)
CoW 系统的 GC 问题:
1. 文件 "A" 占用 Bucket 1 (Blocks 0-9)
2. 文件 "A" 被修改 → CoW 写入 Bucket 2
3. Bucket 1 中的部分 Block 不再被引用
4. Bucket 1 变成"部分垃圾"
5. 需要 GC 来回收空间
GC 流程:
┌──────────────────────────────────┐
│ 1. 扫描 B-Tree,标记存活数据块 │
│ 2. 扫描 Bucket,识别垃圾数据 │
│ 3. 将存活数据复制到新 Bucket │
│ 4. 释放旧 Bucket │
└──────────────────────────────────┘
GC 的开销:
- CPU: 扫描和复制数据
- I/O: 读取旧数据,写入新数据
- 时间: 大文件系统可能需要数分钟到数小时
3.6 多设备架构
3.6.1 设备角色
Bcachefs 支持将不同类型的设备分配不同角色:
角色类型:
- foreground (前台): 主要数据存储
- background (后台): 大容量存储(如 HDD)
- journal (日志): 专用日志设备
- promote (提升): 热数据缓存(如 NVMe)
分层存储架构:
┌───────────────────────────────────────────┐
│ 应用程序层 │
├───────────────────────────────────────────┤
│ Bcachefs 文件系统 │
│ ┌─────────┐ │
│ │ Promote │ ← NVMe SSD (热数据缓存) │
│ └────┬────┘ │
│ ↓ 热数据提升 │
│ ┌─────────┐ │
│ │Foreground│ ← SSD (主存储) │
│ └────┬────┘ │
│ ↓ 冷数据降级 │
│ ┌─────────┐ │
│ │Background│ ← HDD (大容量) │
│ └─────────┘ │
└───────────────────────────────────────────┘
3.6.2 数据放置策略
# 指定设备角色
sudo bcachefs format \
--foreground_target=ssd \
--background_target=hdd \
--promote_target=nvme \
--label=ssd /dev/sda \
--label=hdd /dev/sdb \
--label=nvme /dev/nvme0n1
3.6.3 多设备元数据管理
多设备 B-Tree:
┌────────────────────────────────────────────┐
│ B-Tree 节点 │
│ ┌──────────────────────────────────────┐ │
│ │ Key: Inode 1234, Offset 0 │ │
│ │ Ptr 0: dev=0 (ssd), bucket=100 │ │
│ │ Ptr 1: dev=1 (hdd), bucket=500 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ Key: Inode 1234, Offset 64K │ │
│ │ Ptr 0: dev=0 (ssd), bucket=101 │ │
│ │ Ptr 1: dev=1 (hdd), bucket=501 │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘
每个数据块可以有多个副本存储在不同设备上
3.7 校验和与完整性保护
3.7.1 校验和层次
Bcachefs 完整性保护层次:
1. 数据校验和 (Data Checksum):
┌──────────────────────────────────┐
│ 数据块: "Hello World" │
│ 校验和: crc32c("Hello World") │
│ 存储在 B-Tree 节点的 value 中 │
└──────────────────────────────────┘
2. 元数据校验和 (Metadata Checksum):
┌──────────────────────────────────┐
│ B-Tree 节点数据 │
│ 校验和: sha256(节点数据) │
└──────────────────────────────────┘
3. Journal 校验和:
┌──────────────────────────────────┐
│ Journal 条目数据 │
│ 校验和: (Journal 条目) │
└──────────────────────────────────┘
3.7.2 校验和算法对比
| 算法 | 速度 | 碰撞概率 | 适用场景 |
|---|---|---|---|
| crc32c | ~15 GB/s | 2^-32 | 默认,适合大多数场景 |
| crc64 | ~10 GB/s | 2^-64 | 需要更强校验 |
| xxhash | ~25 GB/s | 2^-64 | 性能优先 |
| sha256 | ~0.5 GB/s | 2^-128 | 安全要求极高 |
# 创建文件系统时指定校验和算法
sudo bcachefs format /dev/sdX --checksum=xxhash
# 查看当前校验和算法
sudo bcachefs show-super /dev/sdX | grep checksum
3.8 压缩实现
3.8.1 压缩流程
写入路径:
┌─────────┐ ┌──────────┐ ┌──────────┐
│ 用户数据 │ ──→ │ 压缩引擎 │ ──→ │ 校验和 │ ──→ 写入磁盘
│ 4KB │ │ zstd │ │ 压缩后 │
└─────────┘ └──────────┘ └──────────┘
输出: 1.5KB 计算: crc32c
读取路径:
┌──────────┐ ┌──────────┐ ┌─────────┐
│ 读取磁盘 │ ──→ │ 校验和 │ ──→ │ 解压缩 │ ──→ 用户数据
│ 压缩数据 │ │ 验证 │ │ zstd │ 4KB
└──────────┘ └──────────┘ └─────────┘
3.8.2 压缩与 CoW 的交互
注意: Bcachefs 的压缩是在 extent 级别进行的
1. 小文件 (< bucket_size):
整个文件压缩后存入一个 bucket
→ 压缩效果最好
2. 大文件:
按 extent 分块压缩
→ 每个 extent 独立压缩
3. 不可压缩数据:
检测到压缩比 < 1.1 时跳过
→ 避免浪费 CPU
3.9 与其他文件系统架构对比
设计理念对比
| 维度 | ext4 | Btrfs | ZFS | Bcachefs |
|---|---|---|---|---|
| 元数据结构 | 表 (table-based) | B-Tree | B+Tree | B-Tree of B-Trees |
| 数据管理 | Extent | Extent | Block | Bucket + Extent |
| CoW | 否 | 是 | 是 | 是 |
| 日志 | 独立 Journal | COW Log Tree | ZIL/SLOG | Journal |
| 校验和 | 否 | 是 | 是 | 是 |
| 分配单元 | Block Group | Chunk | vdev | Bucket |
内核位置对比
Linux 内核文件系统层次:
VFS (Virtual File System)
├── ext4 ← 内核模块, 非常成熟
├── XFS ← 内核模块, 非常成熟
├── Btrfs ← 内核模块, 15年+ 历史
├── bcachefs ← 内核模块, 主线 2024 年加入
│
└── (非主线)
├── ZFS ← 需要 DKMS/OpenZFS 模块
└── NTFS-3G ← FUSE 用户空间实现
3.10 关键数据结构
3.10.1 Superblock (超级块)
Superblock 存储在设备的固定位置,包含:
┌──────────────────────────────────────────────┐
│ Magic Number: 0xca23f8b6bcacfe48 │
│ Version: (major, minor) │
│ UUID: 文件系统唯一标识 │
│ Block Size: 通常 4KB │
│ Bucket Size: 分配单元大小 │
│ Devices[]: 设备列表及角色 │
│ Journal Location: 日志位置 │
│ Root B-Tree: 根 B-Tree 指针 │
│ Creation Time: 创建时间 │
│ Checksum: 超级块自身的校验和 │
└──────────────────────────────────────────────┘
# 查看超级块信息
sudo bcachefs show-super /dev/sdX
3.10.2 Inode 结构
Inode (索引节点) 存储文件元信息:
┌──────────────────────────────────────────────┐
│ Mode: 文件类型和权限 │
│ UID/GID: 所有者信息 │
│ Size: 文件大小 │
│ Atime/Mtime/Ctime: 时间戳 │
│ Nlink: 硬链接计数 │
│ Flags: 文件标志(压缩、加密等) │
│ Hash Seed: 目录哈希种子 │
└──────────────────────────────────────────────┘
3.11 本章小结
| 概念 | 要点 |
|---|---|
| B-Tree | Bcachefs 使用 B-Tree of B-Trees 存储所有元数据 |
| CoW | 写时复制保证崩溃一致性,但有写放大 |
| Journal | 前写日志进一步保障一致性 |
| Bucket | 基本分配单元,大小影响空间效率和元数据开销 |
| 校验和 | 数据 + 元数据 + Journal 三层保护 |
| 多设备 | 支持 SSD/HDD 分层、数据迁移 |
理解这些概念后的操作意义
- Bucket 大小 → 影响小文件的空间效率
- CoW 写放大 → 影响 HDD 的随机写性能
- Journal 设备 → 可用 NVMe 加速元数据操作
- 校验和选择 → 权衡安全性与性能
- GC 策略 → 影响长期使用的性能稳定性
扩展阅读
- B-Tree 维基百科
- Copy-on-Write 详解
- Bcachefs 源码 (fs/bcachefs/)
- Bcachefs B-Tree 实现详解
- LWN: An introduction to bcachefs
上一章: ← 第 2 章:安装 | 下一章: 第 4 章:基础操作 →