Apache BookKeeper Ledger 的底层存储机制解析

发布于:2025-04-04 ⋅ 阅读:(40) ⋅ 点赞:(0)

Apache BookKeeper 的 ledger(账本)是其核心数据存储单元,底层存储机制结合了日志追加(append-only)、分布式存储和容错设计。Ledger 的数据存储在 Bookie 节点的磁盘上,具体实现涉及 Journal(日志)和 Ledger Storage(账本存储)两个部分。以下是 ledger 底层存储数据的详细机制:


Ledger 存储的整体架构

  1. 分布式存储
    • 一个 ledger 的数据分布在多个 Bookie 节点上(由 ensembleSize 定义,例如 3 个节点)。
    • 每个 Bookie 负责存储 ledger 的一部分或全部数据(取决于 writeQuorum 配置)。
  2. 两阶段存储
    • Journal:实时记录写入操作的事务日志,确保数据持久化。
    • Ledger Storage:长期存储账本数据,优化读取性能。
  3. 文件系统
    • 数据直接存储在 Bookie 节点的本地文件系统中(例如 ext4、XFS),没有额外的数据库层。

底层存储的实现细节

1. Journal(日志)
  • 作用
    • Journal 是 ledger 数据写入的第一步,用于保证数据持久性和一致性。
    • 每次写入条目(entry)时,先追加到 Journal,确保即使系统崩溃也能恢复。
  • 存储位置
    • 配置项 journalDirectory 指定路径(例如 /bookkeeper/journal)。
    • 每个 Bookie 节点独立维护自己的 Journal。
  • 文件结构
    • Journal 由多个日志文件组成,按时间或大小滚动(rollover)。
    • 文件名格式:journal.<timestamp>(例如 journal.1698765432100)。
    • 每个文件是一个顺序追加的二进制文件。
  • 写入过程
    1. 客户端发送条目到 Bookie。
    2. Bookie 将条目序列化为二进制格式,包含:
      • Ledger ID:账本标识。
      • Entry ID:条目序列号。
      • Data:实际数据内容。
    3. 追加到当前 Journal 文件。
    4. 可配置 journalSyncData=true(默认),调用 fsync 强制刷盘,确保数据持久化。
    5. 返回确认(ACK)给客户端。
  • 性能优化
    • Journal 使用顺序写入,适合高吞吐量。
    • 建议将 journalDirectory 放在高速磁盘(例如 SSD)上。
2. Ledger Storage(账本存储)
  • 作用
    • Journal 确认后,数据异步写入 Ledger Storage,用于长期存储和读取。
  • 存储位置
    • 配置项 ledgerDirectories 指定路径(例如 /bookkeeper/ledgers)。
    • 可以配置多个目录(例如 /disk1/ledgers, /disk2/ledgers),分散 I/O 负载。
  • 文件结构
    • Ledger 数据按 ledger 分片存储,目录结构:
    • /bookkeeper/ledgers/
      ├── current/          # 当前活跃的账本文件
      │   ├── 00000001.log  # Ledger ID 1 的数据文件
      │   ├── 00000002.log  # Ledger ID 2 的数据文件
      ├── recovered/        # 崩溃恢复后的文件
      └── compacted/        # 压缩后的文件(可选)

    • 每个 .log 文件对应一个 ledger,包含该 ledger 的所有条目。
  • 写入过程
    1. Journal 写入成功后,条目放入内存缓冲区(EntryLogger)。
    2. 缓冲区满或达到刷新间隔(ledgerStorage_flushInterval)时,异步写入 .log 文件。
    3. 数据按 Entry ID 顺序存储,文件格式为二进制。
  • 索引
    • 为了快速定位条目,BookKeeper 维护一个索引。
    • 配置项 indexDirectories 指定路径(默认与 ledgerDirectories 相同)。
    • 默认使用文件系统索引(FileInfo),可选配置 RocksDB(dbStorage_rocksDB_* 参数)提高性能。
    • 索引记录每个 Entry ID 在 .log 文件中的偏移量。
3. 数据分布
  • Ensemble
    • 一个 ledger 的数据分布在 ensembleSize 个 Bookie 上。
    • 例如,ensembleSize=3,数据可能存储在 bookie1、bookie2、bookie3。
  • Write Quorum
    • 每次写入,数据完整存储在 writeQuorumSize 个 Bookie 上。
    • 如果 writeQuorum < ensembleSize,不同条目可能分布在不同的 Bookie 子集。
  • 副本
    • 每个条目在多个 Bookie 上有副本(由 writeQuorum 控制),提供容错性。

数据写入的完整流程

以 ensembleSize=3, writeQuorum=3, ackQuorum=2 为例:

  1. 客户端
    • 创建 ledger,分配 Ledger ID=1,选择 bookie1、bookie2、bookie3 作为 ensemble。
    • 发送条目 entry1 到 3 个 Bookie。
  2. Bookie
    • bookie1:写入 /journal/journal.<timestamp>,返回 ACK。
    • bookie2:写入 /journal/journal.<timestamp>,返回 ACK。
    • bookie3:写入 /journal/journal.<timestamp>,返回 ACK(可能稍慢)。
    • 客户端收到 2 个 ACK(满足 ackQuorum=2),写入成功。
  3. 异步存储
    • 每个 Bookie 将 entry1 从 Journal 移到 /ledgers/current/00000001.log。
    • 更新索引,记录 entry1 的偏移量。

数据读取

  • 读取流程
    1. 客户端指定 Ledger ID 和 Entry ID。
    2. Bookie 从索引查找条目位置。
    3. 从 .log 文件读取数据返回。
  • 容错
    • 如果某个 Bookie 不可用,客户端从其他副本读取(需要至少 ackQuorum 个副本可用)。

存储特性

  1. 追加式存储
    • Ledger 只支持追加写入(append-only),不支持修改或删除。
    • 删除 ledger 需要关闭并通过 ZooKeeper 删除元数据。
  2. 纠删码(Erasure Coding)
    • 默认不使用纠删码,而是完整副本存储。
    • 可通过配置启用纠删码(实验性功能),减少存储开销。
  3. 持久性
    • Journal 的 fsync 保证写入持久化。
    • Ledger Storage 异步写入,依赖 Journal 恢复一致性。

崩溃恢复

  • Journal 回放
    • Bookie 重启时,检查 Journal 文件,恢复未写入 Ledger Storage 的条目。
    • 恢复后,数据移到 recovered/ 目录。
  • 一致性
    • 只要 ackQuorum 个 Bookie 存活,数据不会丢失。

性能优化

  1. 分离存储
    • 将 journalDirectory 和 ledgerDirectories 放在不同磁盘(例如 SSD 和 HDD),提高 I/O 性能。
  2. 批量写入
    • Journal 支持批量 fsync,减少磁盘同步开销。
  3. 索引优化
    • 使用 RocksDB 替代默认文件索引,加速查找。

总结

Ledger 的底层存储机制:

  • Journal:顺序写入事务日志,保证持久性,存储在 journalDirectory。
  • Ledger Storage:异步存储账本数据,分布在 ledgerDirectories 的 .log 文件中。
  • 索引:记录条目偏移量,存储在 indexDirectories。
  • 分布式:数据按 ensembleSize 分布在多个 Bookie,副本数由 writeQuorum 控制。

这种设计结合了高吞吐量(顺序写入)、低延迟(异步存储)和容错性(多副本),非常适合分布式日志存储需求。你的 Go Demo 数据最终存储在 3 个 Bookie 的 Journal 和 Ledger 文件中,具体路径取决于 Docker Compose 的卷配置


网站公告

今日签到

点亮在社区的每一天
去签到