首页 > 其他分享 >BookKeeper 介绍(1)--简介

BookKeeper 介绍(1)--简介

时间:2024-05-26 17:34:09浏览次数:28  
标签:-- 简介 写入 ledger BookKeeper entry 日志 节点

BookKeeper 是一个可扩展、可容错和低延迟的存储服务;本文主要介绍其基本概念及特性。

1、基本概念

在 BookKeeper 中:

  • 日志的单元是 entry (又名 record)
  • 日志 entries 流称为 ledgers
  • 存储 ledgers 的独立服务器称为 bookies

BookKeeper 被设计为可靠且能够抵御各种故障。bookies 可能会崩溃、损坏数据或丢弃数据,但只要在整个集合中有足够数量的 bookies 正常工作,服务将能够正常运行。

1.1、Entries

Entries 包含写入 ledgers 的数据数据,以及一些重要的元数据。Entries 是写入 ledgers 的字节序列,每个 entry 有如下字段:

字段 Java 类型 说明
Ledger number long Ledger ID
Entry number long Entry ID
Last confirmed (LC) long 最后提交的 Entry ID
Data byte[] entry 的数据
Authentication code byte[] 消息认证码,包含 entry 中的所有其他字段

1.2、Leaders

Ledgers 是 BookKeeper 中的基本存储单元。Ledgers 是一系列 entries 的序列,每个 entry 是一系列字节。entries 按以下方式写入 ledges:

  • 顺序写入
  • 最多写入一次

这意味着 ledger 具有仅追加的语义。一旦条目被写入ledger,就不能修改。确定正确的写入顺序是客户端应用程序的责任。

1.3、Bookies

Bookies 是处理 ledger(更具体地说是 ledger 的片段)的单个 BookKeeper 服务器。Bookies 作为集合的一部分运行。一个 bookie 是一个独立的 BookKeeper 存储服务器。独立的 bookies 存储 ledger 的片段,而不是整个ledger(出于性能考虑)。对于给定的 ledger L,一个集合是存储 L 中 entries 的 bookies 组。每当 entries 被写入 ledger 时,这些 entries 会被分布式地存储在集群中的 bookies 的子组中,而不是存储在所有的 bookies 上。

1.4、元数据存储

BookKeeper 元数据维护着 BookKeeper 集群的所有元数据,包括 ledger 元数据、可用的 bookie 等等。目前,BookKeeper 使用 ZooKeeper 进行元数据存储。

1.5、bookies 中的数据管理

Bookies 以一种日志结构的方式管理数据,通过三种类型的文件来实现:journals、entry logs、index files。

1.5.1、Journals

一个 journal 文件包含了 BookKeeper 的事务日志。在对 ledger 进行任何更新之前,bookie 确保将描述该更新的事务写入到非易失性存储中。一旦 bookie 启动或者旧的 journal 文件达到文件大小阈值,就会创建一个新的 journal 文件。

1.5.2、Entry logs

一个 entry 日志文件管理来自 BookKeeper 客户端的已写入条目。来自不同 ledgers 的 entries 被聚合并按顺序写入,而它们的偏移量被保存在 ledger 缓存中作为指针,以便进行快速查找。
一旦 bookie 启动或者旧的 entry 日志文件达到文件大小阈值,就会创建一个新的 entry 日志文件。一旦旧的 entry 日志文件与任何活跃的 ledger 都没有关联,垃圾收集线程就会删除它们。

1.5.3、Index files

为每个 ledger 创建一个索引文件,它包括一个头部和若干固定长度的索引页,记录了存储在 entry 日志文件中的数据的偏移量。
由于更新索引文件会引入随机磁盘 I/O,索引文件通过后台运行的同步线程进行延迟更新。这确保了更新的快速性能。在索引页被持久化到磁盘之前,它们会被放到 ledger 缓存以便查找。

1.5.4、Ledger cache

Ledger 索引页被缓存在内存池中,这使得磁盘头调度的管理更加高效。

1.5.5、Adding entries

当客户端需要将一个 entry 写入 ledger 时,entry 需经过如下几个步骤才能持久化到磁盘上:

  1. entry 被追加到 entry 日志中
  2. entry 的索引在 ledger 缓存中被更新
  3. 与此 entry 更新对应的事务被追加到 journal 中
  4. 响应被发送给 BookKeeper 客户端

出于性能考虑,entry 日志在内存中缓冲 entry 并批量提交它们,而 ledger 缓存则在内存中保存索引页并延迟刷新它们。

1.5.6、Data flush

Ledger 索引页在以下两种情况下会刷新到索引文件中:

  1. 当达到 ledger 缓存的内存限制时。没有更多空间可用来保存新的索引页。脏索引页将被从 ledger 缓存中驱逐,并持久化到索引文件中。
  2. 后台线程负责定期从 ledger 缓存中将索引页刷新到索引文件中。

除了刷新索引页之外,同步线程还负责在 journal 文件使用过多磁盘空间的情况下滚动 journal 文件。同步线程中的数据刷新流程如下:

  • 1、在内存中记录 LastLogMark。LastLogMark 表示其之前的所有 entries 都已被持久化(包括索引和 entry 日志文件),包含两个部分:

  txnLogId(journal 文件ID)
  txnLogPos(journal 文件中的偏移量)

  • 2、从 ledger 缓存中刷新脏索引页到索引文件,并刷新 entry 日志文件以确保所有缓存的 entries 都已持久化到磁盘。

  理想情况下,一个 bookie 只需要刷新包含在 LastLogMark 之前的索引页和 entry 日志文件。但是,在与 journal 文件对应的 ledger 和 entry 日志中没有这样的信息。因此,线程在这里完全刷新了 ledger 缓存和 entry 日志,可能会刷新 LastLogMark 之后的 entry。尽管刷新更多 entry 不是问题,但是多余的。

  • 3、LastLogMark 被持久化到磁盘,这意味着在 LastLogMark 之前添加的 entry 数据和索引页也已被持久化到磁盘。现在是安全地删除早于 txnLogId 的 journal 文件的时候了。

如果在将 LastLogMark 持久化到磁盘之前,bookie 崩溃了,journal 文件仍然包含可能尚未持久的 entries。因此,当 bookie 重新启动时,它会检查 journal 文件以恢复这些 entries,数据不会丢失。

使用上述数据刷新机制,当 bookie 关闭时,同步线程跳过数据刷新是安全的。然而,在 entry 记录器中,它使用一个缓存通道来批量写入 entries,而在关闭时缓存通道中可能有数据。bookie 需要确保在关闭时刷新缓存通道中的 entry 数据。否则,entry 日志文件将因缺少部分 entries 而损坏。

1.5.7、Data compaction

在 bookies 中,不同 ledgers 的 entries 被交错存储在 entry 日志文件中。每个 bookie 运行一个垃圾收集器线程来删除未关联的 entry 日志文件,以回收磁盘空间。如果某个 entry 日志文件包含尚未删除的 ledger 的 entries,则该 entry 日志文件将永远不会被移除,占用的磁盘空间也永远无法回收。为了避免这种情况,bookie 服务器在垃圾收集器线程中压缩 entry 日志文件,以回收磁盘空间。

有两种不同频率运行的压缩:次要压缩和主要压缩。次要压缩和主要压缩之间的区别在于它们的阈值和压缩间隔。

  1. 垃圾收集阈值是未删除的 ledgers 占据的 entry 日志文件大小的百分比。默认的次要压缩阈值为 0.2,而主要压缩阈值为 0.8。
  2. 垃圾收集间隔是运行压缩的频率。默认的次要压缩间隔为 1 小时,而主要压缩间隔为 1 天。

如果阈值或间隔设置为小于或等于零,压缩将被禁用。

垃圾收集器线程中的数据压缩流程如下:

  1. 该线程扫描 entry 日志文件以获取 entry 元数据,其中记录了包含 entry 数据的 ledgers 列表。
  2. 在正常的垃圾收集流程中,一旦 bookie 确定某个 ledger 被删除,该 ledger 将从 entry 元数据中删除,同时 entry 日志的大小将减少。
  3. 如果 entry 日志文件的剩余大小达到指定的阈值,entry 日志文件中活跃的 ledgers 的 entries 将被复制到一个新的 entry 日志文件中。
  4. 一旦所有有效 entries 都已复制,旧的 entry 日志文件将被删除。

1.5.8、ZooKeeper metadata

BookKeeper 需要安装 ZooKeeper 来存储 ledger 元数据。构建 BookKeeper 客户端对象时,需要将 ZooKeeper 服务器列表作为参数传递给构造函数,如下所示:

String zkConnectionString = "127.0.0.1:2181";
BookKeeper bkClient = new BookKeeper(zkConnectionString);

1.5.9、Ledger manager

ledger 管理器处理 ledger 的元数据(存储在 ZooKeeper 中)。BookKeeper 提供了两种类型的 ledger 管理器:flat ledger manager 和 hierarchical ledger manager。这两种 ledger 管理器都扩展了 AbstractZkLedgerManager 抽象类。

A、Hierarchical ledger manager

默认的 ledger 管理器。Hierarchical ledger manager 能够管理大量的 BookKeeper 账本(> 50,000)。

在 HierarchicalLedgerManager 类中实现了 hierarchical ledger manager,首先使用 EPHEMERAL_SEQUENTIAL znode 从 ZooKeeper 获取全局唯一标识符。由于 ZooKeeper 的序列计数器采用 %10d(10位数字,用 0 填充,例如 <path>0000000001)的格式,hierarchical ledger manager 将生成的 ID 分为 3 部分:

{level1 (2 digits)}{level2 (4 digits)}{level3 (4 digits)}

这三个部分被用来构成实际的 ledger 节点路径,以存储 ledger 元数据:

{ledgers_root_path}/{level1}/{level2}/L{level3}

例如,ledger 0000000001 被分为三个部分,分别是 00、0000 和 00001,然后存储在 znode "/{ledgers_root_path}/00/0000/L0001" 中。每个 znode 可以包含多达 10,000 个 ledger,这避免了子列表大于最大 ZooKeeper 数据包大小的问题(正是这个限制促使创建 Hierarchical ledger manager)。

B、Flat ledger manager

自 4.7.0 版本起已被弃用,现在不建议使用。

在 FlatLedgerManager 类中实现了 Flat ledger manager,它将所有 ledger 的元数据存储在单个 ZooKeeper 路径的子节点中。Flat ledger manager 创建顺序节点以确保 ledger ID 的唯一性,并将所有节点前缀设置为 L。Bookie 服务器在哈希映射中管理其自己的活动 ledgers,以便轻松查找已从 ZooKeeper 中删除的 ledgers,然后对其进行垃圾回收。

2、Bookkeeper 特性

2.1、节点对等架构

创建 ledger 时有 3 个控制数据写入方式的参数:ensSize 选择几个节点存储这个 ledger,writeQuorumSize 控制数据写几个副本(并发写,不同于 Kafka 或 HDFS,BookKeeper 数据节点之间没有主从关系,数据同步从服务端移到了客户端),ackQuorumSize 表示几个副本 ack 就表示写入成功。
以下图为例,createLedger(5,3,2),在保存 ledger 时,选择了 5 个节点,但是只写 3 个副本,2 个副本 ack 就表示写入成功。第一个参数一般可用于调整并发度,因为写 3 个副本是通过轮转的方式写入,例如第 1 个 entry 是写 1-3 节点,第 2 个 entry 写 2-4 节点,第 3 个 entry 写 3-5 节点,第 4 个 entry 写 4-5 和 1 节点这样轮转。这种方式即便 3 个副本,也可以把 5 个节点都用起来。

这几个参数可让用户通过机架感知、机房感知等各种方式进行灵活设置。当选好节点后节点之间的排序就已完成,每个 entry 会带个 index,index 和节点已有绑定关系,例如 index 为1 的,都放在 123上,为 2 的都放在 234 上。通过这种方式可以让我们知道每个节点存了哪些消息,当某个节点宕机,根据这个节点的位置信息,把对应 entry 还在哪些节点上有副本的信息找出来进行多对多的恢复。这么做的另一个好处是不用再维护元数据信息,只需要有每个节点 entry index 信息,在 createLedger 时记录好每个节点的顺序即可。

createLedger(5,3,2)数据存储结构是下图中右边的结构,如果选择 ensSize=3,writeQuorumSize=3,数据存储结构就是下图中左边的结构:

 综上,可以通过 ensSize 来调整读写带宽,通过 writeQuorumSize 调整强一致性的控制,通过 ackQuorumSize 权衡在有较多副本时也可以有较低的时延(但一致性就可能有一定的损失)。

2.2、可用性

A、读可用性

读的访问是对等的,任意一个节点返回就算读成功。这个特性可以把延迟固定在一个阈值内,当遇到网络抖动或坏节点,通过延迟参数避障。例如读的延迟时间为 2ms,读节点 3 时超过 2ms,就会并发地读节点 4,任意一个节点返回就算读成功,如上面图 1 中 Reader 部分。

B、写可用性

在 createLedger 时会记录每个节点的顺序,假如写到 5 节点宕机,会做一次元数据的变更,从这个时间开始,先进行数据恢复,同时新的 index 中会把 5 节点变为 6 节点,如上面图 1 中 x 节点替换 5 节点。

2.3、一致性

BookKeeper 底层节点对等设计让写入数据的 Writer 成为了协调者,Writer 来保存数据是否存储成功的状态,例如节点是否出现问题、副本够不够、在写入过程中出现宕机时通过 fencing 的方式防止脑裂等。所以,Writer 维护了 2 个 index:LastAddPushed 和 LastAddConfirmed。
LastAddPushed 记录最后写入的 entry;LastAddConfirmed 记录最后连续成功写入的 entry,需要确保 LastAddConfirmed 之前的 entry 都已写入成功,例如有 id 为 1、2 、3  的 entry,1 和 3 的 entry 先返回成功了,2 的 entry 还未返回,按连续的规则 LastAddConfirmed 是 1 而不是 3。

2.4、读写分离

下图是每个数据节点的数据流转过程,数据写入时,Writer 通过 append only 方式写入到 Journal,Journal 在把数据写到内存的同时会按一定频率(默认 1ms 或 500 byte)把数据持久化到 Journal Device 里,写完后会告诉 Writer 这个节点写入成功了(持久化到磁盘是默认配置)。

读的时候,如果读最新的数据,可以直接从内存中返回,如果读历史数据,也只去读数据盘,不会对 Journal Device 写入有影响。这样针对有读瓶颈或写瓶颈的用户,可以把 Journal Disk 或 Ledger Disk换成 SSD 盘,提升性能,并且防止读写的互相干扰。

 

 

参考:
https://bookkeeper.apache.org/docs/overview/
https://cloud.tencent.com/developer/article/2054521

标签:--,简介,写入,ledger,BookKeeper,entry,日志,节点
From: https://www.cnblogs.com/wuyongyin/p/18150113

相关文章

  • FPGA交通灯进阶
    1.进阶要求➢十字路分为主干道Highway和乡村公路Farmroad;➢路口的检测器C,没有检测到Farmroad上有等候的车子,Highway上的交通灯(HL)保持为绿灯;➢C检测到Farmroad上有车后HL再保持一段时间(例如30秒)才通过黄灯(例如3秒)变为红灯,同时Farmroad上的交通灯(FL)从红灯变为绿灯;➢Farm......
  • 数据库——查询树形结构某节点的所有子节点、所有父节点
    一、ORACLE查询当前节点与所有子节点:SELECT*FROMYW_XYZBCONNECTBYPRIORID=PARENT_IDSTARTWITHID='***'查询当前节点与所有父节点:SELECT*FROMYW_XYZBCONNECTBYPRIORPARENT_ID=IDSTARTWITHID='***'二、PgSQL查询当前节点与所有子节点:WITHRECU......
  • Windows、Linux下,基于QT的打包方法
    整理这篇文档的意义在于:自己走了很多弯路,淋过雨所以想为别人撑伞,也方便回顾,仅供参考ps:第一次做Windows下打包,用了2小时,第二次20秒第一次做Linux(ubuntu)下打包,用了8小时,第二次1分半一、Windows有许多比较坑的帖子,会带新人走不少弯路,大家注意鉴别(没方法,随缘)1、首先,找到......
  • 32bit的linux系统内存分布
    32bit的linux系统内存分布​ 在32bit的linux系统,则每个运行的程序都会得到4G大小的内存空间,只不过每个程序得到的4G大小的内存都是虚拟内存,而物理内存才只有4G,物理内存是真实存在的,而虚拟内存是通过映射得到的。(如图所示一个程序有1M大小的物理内存,在运行之后会得到4G大小的虚......
  • Java控制台实现斗地主的洗牌和发牌功能
    一、题目要求  有3个玩家:A,B,C。底牌有三张牌,每个人共17张牌,共(17*3+3=54)张牌,实现洗牌与发牌,只在控制没有实现UI可视化。二、思路1、用List集合存储所有的扑克牌。2、洗牌:随机打乱牌的顺序,用到Collections工具类。publicstatic void shuffle(List<?> list)使用默......
  • LVS精益价值管理系统DownLoad.aspx存在任意文件读取漏洞
    漏洞描述该漏洞允许攻击者通过构造特定的请求,读取服务器上的任意文件,从而可能导致敏感信息泄露。DownLoad.aspx页面接受一个文件路径作为参数,并直接将其传递给文件读取函数。这个函数没有经过严格的检查和过滤,攻击者可以通过构造恶意请求,包含相对路径或绝对路径,从而读取服务器上......
  • 程序员都讨厌写文档?这4个工具让你事半功倍
    对于一般的程序员来说,花费数小时来创建代码或修改现有代码只是一天工作中的一部分,简而言之,当程序员忙于他们最新的项目时,他们会把自己与外界隔绝,这种刻板印象还是有一定道理的。但是你没发现的是其实很多程序员还需要是保存关于他们的代码如何工作的文档,事实上,很多程序员都不......
  • P1504 积木城堡/01背包板子
    代码:#define_CRT_SECURE_NO_WARNINGS#include<iostream>#include<vector>#include<algorithm>#include<math.h>#include<sstream>#include<string>#include<string.h>#include<iomanip>#include<stdlib.h>......
  • C语言之指针详解(2)
    文章目录一、数组名的理解二、使用指针访问数组三、一维数组传参的本质四、二级指针五、指针数组六、指针数组模拟二维数组经过了上一讲的《C语言之指针详解(1)》的学习后,我想大家对于指针已经有了一定程度的了解,这章我们将继续学习C语言。这章主要讲的是数组与指针之......
  • 操作抖音小店一直不出单怎么办?只需要做好这两点就可以了!
    大家好,我是电商小V最近很多新手小伙伴来咨询我说自己操作抖音小店,自己的店铺长时间不出单应该怎么办?今天咱们就来详细的说一下,咱们要清楚的就是自己的店铺不出,只需要咱们做好这两点就可以了,第一点:店铺的选品工作咱们做电商都知道,选品是非常重要的,俗话说7分靠选品3分靠运......