当前位置: 首页 > news >正文

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

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 的卷配置

http://www.dtcms.com/a/108864.html

相关文章:

  • 配置单区域OSPF
  • ARM—LED,看门狗关闭,按钮,时钟,PWM定时器,蜂鸣器
  • 【前端扫盲】postman介绍及使用
  • 走向多模态AI之路(三):多模态 AI 的挑战与未来
  • 【家政平台开发(12)】家政平台数据库设计:从MySQL到MyBatis-Plus实战
  • 多个参考文献插入、如何同时插入多个参考文献:如[1,2]、[1-3]格式
  • 搬砖--贪心+排序的背包
  • 请谈谈分治算法,如何应用分治算法解决大规模问题?
  • Pico4 Pro VR 和HTC Vivi 哪个好些
  • ngx_getpid() ngx_parent = ngx_getppid()
  • [C语言笔记]09、指针
  • 代码随想录Day31
  • 作用域与上下文:JavaScript魔法森林探秘
  • Python每日一题(14)
  • J1 ResNet-50算法实战与解析
  • JavaScript学习18-css操作和事件处理程序(html/DOM0/DOM2)
  • 蓝桥杯Java B组省赛真题高频考点近6年统计分类
  • 职坐标解析AI风口职业发展新机遇
  • 用大语言模型学文学常识
  • 一个alignment trap的解决办法
  • TRDI 公司的RiverPro 和 RioPro ADCP 用户指南
  • UE5 Cast To的作用
  • 图形库 EasyX - EasyX 初识(EasyX 概述、EasyX 下载与安装、打开一个窗口、打开一个彩色窗口、绘制简易图形、输出文字)
  • .NET 创建MCP使用大模型对话二:调用远程MCP服务
  • 本地化部署DeepSeek-R1蒸馏大模型:基于飞桨PaddleNLP 3.0的实战指南
  • OC添加滑块验证码
  • XLRS-Bench:您能否理解极端大型超高分辨率遥感影像?
  • 预测分析(三):基于机器学习的分类预测
  • 基于微信小程序的生签到系统设计与实现
  • c语言数据结构--------拓扑排序和逆拓扑排序(Kahn算法和DFS算法实现)