Apache BookKeeper是一个可扩展、可容错以及低延迟的专为实时场景优化的日志存储服务。BookKeeper最初由Yahoo!研究院开发,然后作为Apache ZooKeeper的子项目在2011年开始孵化,最终于2015年01月毕业成为Apache顶级项目。自面世以来,BookKeeper被广泛应用于诸如Twitter、Yahoo、Salesforce等企业中,存储和服务了重要关键数据并支撑了许多不同的场景。在这篇博客中,我将概述一下BGookKeeper是如何确保持久性、一致性和低延时的,并且我会强调BookKeeper的这些承诺和关键功能,全部都是开箱即用并开源的形式提供的。
在
上文
中,我介绍了Apache BookKeeper的概况以及BookKeeper的一些概念与术语。一个BookKeeper集群是这样组成的:
一组独立的存储服务器,成为bookies
一个元数据存储系统,提供服务发现以及元数据管理服务
BookKeeper客户端可以使用高级DistributedLog API(又称日志流API)或是底层账簿API来直接与Bookie交互。一个典型的BookKeeper部署如下面图1所示:
图1:一个典型的BookKeeper部署(外加通过两种API连接的应用)
流存储需求
如同我们在前文中介绍的那样,一个实时存储平台需要满足一下所有的需求:
客户端必须可以以很低的延时(小于5ms)读写流数据,即使同时还要确保数据的持久性存储
数据存储必须是持久的、一致的,以及可以容忍故障的
系统需要能够确保数据读写顺序一致
系统需要能够同时提供对历史数据以及实时数据的访问
BookKeeper通过如下承诺满足了所有上述需求:
承诺描述
复制为了容忍故障,数据被复制到不同机器的持久性存储上,甚至可以是多数据中心
持久性数据复制成功前会提交到持久性存储设备上。在通知客户端写入成功前会强制使用fsync
一致性一个简单、可重复度的一致性模型被用来确保数据读者之间的一致性
可用性集群结构调整以及speculative reads被用以改善读写可用性并同时确保一致性和可用性
低延时读写延时通过I/O隔离机制来确保,同时维持一致性和持久性
复制
BookKeeper对所有数据都会复制和存储相同的多份拷贝——一般是三份或是五份——到不同的机器上,可以是同一数据中心,也可以是跨数据中心。不像其他使用主/从或是管道复制算法在副本之间复制数据的分布式系统(例如Apache HDFS、Ceph、Kafka),Apache BookKeeper使用一种多数投票并行复制算法在确保可预测的低延时的基础上复制数据。图2展示了一个BookKeeper集群中数据的复制:
图2:BookKeeper复制机制中集群、写入以及应答的多数群范围划分
在上面的图中:
一组Bookies被从BookKeeper集群中(自动)选出(图中的例子里是Bookie1-5)这些Bookie作为一个整体(ensemble)存储一个特定的账簿。
账簿中的数据被条带化地存储在Bookie中。也就是说,每一条记录都被存储在多个副本中,副本数可以在客户端进行配置,被称为最小写入数。在图中,最小写入数是3,也就是说数据会被写入Bookie2、3、4。
每当一个客户端写入一笔数据,客户端会等待一定数量的副本响应成功。这个数字就是最小响应数。当一定数量副本响应成功后,客户端即可认为写入成功。在上图中,最小响应数是2,也就是说,假如Bookie3/4写入成功,客户端即会被告知写入成功。
当有Bookie发生故障时ensemble结构会发生改变。故障的Bookie会被健康的Bookie所替换,这也许只是暂时性的。比如说,如果Bookie5宕机,BookieX有可能会顶替它。
复制:核心思想
BookKeeper复制基于以下核心思想构成:
日志流的原子结构是记录而不是字节。也就是说,数据总是以不可分割的记录形式(包括了元数据)存放的,而不是一个个字节组成的数组。
日志流中记录的顺序与实际记录的实际存储是解耦的。
这两条核心原理使得BookKeeper可以做到:
提供了选择写入记录Bookie的丰富选项,可以用来保障写入总是能够成功完成,即使ensemble中存在许多故障或是缓慢的Bookie(只要总容量还足以应对)。ensemble结构调整确保了这一点
通过调优最小应答数来降低添加数据的延时。这对BookKeeper如上文所述在确保一致性和持久性的同时确保低延时是至关重要的。
提供了快速的重新复制机制,一种多对多的副本恢复技术(重新复制为那些副本数不足,也就是低于最小写入数的副本创建更多的副本)。所有的Bookie都可以发送或是接收数据副本。
我们将来会在后续文章中与您讨论BookKeeper的具体细节。
持久性
写入BookKeeper的每一条记录都被承诺复制到配置的数量的Bookie的持久化存储上。这是通过显式调用磁盘的fsync以及写入确认实现的。
在单台Bookie上,在返回客户端写入成功的响应前,数据都被显式通过调用fsync刷写进磁盘,在故障时也能保存住数据。这确保了数据被写入可断电的持久化存储设备,以备故障恢复后可以找回并继续使用数据。
一个集群内,数据被复制到多台Bookie上防止故障。
一条记录的写入只有收到一定数量的成功写入响应(由最小响应数确定)之后才会返回客户端写入成功。
近年来的大多数NoSQL类型数据库、分布式文件系统,以及消息系统(比如Apache Kafka)假定简单地把数据复制到多个节点上的内存、存储上就足以满足持久性的要求,但这些系统表现出的问题是他们允许潜在的数据丢失。BookKeeper被设计成更加强壮的持久性承诺,用以完全避免数据丢失以及满足严苛的企业级需求标准。
一致性
分布式系统中的一致性保障是一个很常见的问题,特别是引入复制机制来确保持久性与可用性之后。BookKeeper提供了一种简单但健壮的一致性保障——可重复读级别一致性——对于存储的日志数据来说:
如果一条记录被确认写入了,那么它是立即可被读到的。
如果一条记录被读过一次,那么它永远是可被读到的。
如果一条记录R写入成功了,那么所有R之前被成功写入的记录都是可被读的。
不同Reader之间反复读取记录得到的记录顺序必须是完全一致的。
这种可重复读的一致性是BookKeeper使用了一种叫LastAddConfirmed(LAC)的协议完成的。我将会在后续的文章中给出进一步的细节。
可用性
在CAP(一致性、可用性、分区容忍)理论的语境中,BookKeeper是一个CP系统。然而实际上,Apache BookKeeper提供了非常高的可用性,即使出现硬件、网络或是其他类型故障。为了确保读写的高可用,BookKeeper使用了如下机制:
高可用类型——机制————描述
写入高可用——ensemble变更——Bookie发生故障时客户端会调整记录的存储位置——记录写入的Bookie。这确保了只要集群中剩余总Bookie容量足够的话写入就能成功
读取高可用——任意读取——不同于其他系统(例如Kafka)只允许从被指定为Leader的节点上读取,BookKeeper允许客户端从ensemble中任意Bookie读取这使得读取流量可以由多个Bookie分摊,降低了读取最新数据的延时
低延时
健壮的持久性以及一致性保障是很复杂的分布式系统问题,特别是结合了满足企业及低延时需求的目标时。BookKeeper通过如下方法实现了这些需求:
单个Bookie上,Bookie服务器被设计成隔离了不同类型的工作负载的I/O(写入、读取最新数据、读取历史数据或是随机读取)。一种组提交机制被使用在了日志上,用以权衡延时与吞吐量。
一种多数投票并行复制结构被用来掩盖来自网络故障、JVM垃圾回收停顿以及慢速磁盘导致的延时。这种机制改善了读写尾部数据的延时,并且确保99%的操作的低延时。
通过长轮询机制在数据写入一成功时就立即通知并分发给读取尾部数据的客户端
最后,值得指出的是,显式调用fsync并且等待写入确认确保的持久性以及可重复读一致性对状态处理应用来说是至关重要的,特别是对流式应用中“恰好一次”语义的处理来说尤其如此。
结论
在本文中,我解释了BookKeeper可以承诺的持久性、一致性以及可用性。我希望本文能够为您选用BookKeeper作为您实时工作负载的存储平台提供一个具有说服力的理由。在后续的一些文章中,我将会更加深入地解释BookKeeper的复制机制,以及通过何种机制来确保低延时并同时确保一致性和持久性。
如果您对BookKeeper或是DistributedLog感兴趣,您可以通过邮件列表(http://bookkeeper.apache.org/community/mailing-lists/)加入成长中的BookKeeper社区或是加入我们的Slack频道(https://apachebookkeeper.herokuapp.com/)。您也可以在这里(http://bookkeeper.apache.org/releases/)下载最新版本(目前版本是4.5.0)并开始使用(http://bookkeeper.apache.org/docs/4.5.0/overview/overview/)。
想要了解关于Apache BookKeeper项目更多的一般信息,请访问我们的官方网站(https://bookkeeper.apache.org/)并且关注项目的Twitter(https://twitter.com/asfbookkeeper)和(https://twitter.com/distributedlog)
领取专属 10元无门槛券
私享最新 技术干货