在 Salesforce,我们需要可以同时处理两种流的存储系统:一种流用来预写日志,另一种流用来处理数据。但对这两种流,我们的要求相互矛盾:预写日志流的写入延迟低,而读取吞吐量高;数据流的写入吞吐量高,但随机读取延迟低。作为云计算的领军企业,我们的存储系统必须具备云感知能力(可用性和持久性要求越来越高)。为了在商用硬件上运行并方便扩容,我们无法更改部署模型设计。
在对存储系统进行初步研究后,我们考虑是自己搭建一套还是购买一套。
考虑到整体规划和上市时间、资源、成本等主要的业务驱动因素,我们决定使用开源存储系统。
在查看了开源代码后,我们有两个备选方案:Ceph 和 Apache BookKeeper。由于这套系统需要对客户开放,可扩展性和一致性都很重要,系统必须完全满足用例的 CAP(Consistency:一致性;Availability:可用性;Partition Tolerance:分区容错性)要求以及我们自己的特殊需求。首先,我们来看一下 BookKeeper 和 Ceph 在 CAP 和其他方面的表现。
Ceph 可以保证一致性和分区容错,读取路径可以借助不可靠读取提供可用性和分区容错;但要使写入路径保证可用性和分区容错性并不容易。并且,我们不能更改部署数据。
我们决定选择 Apache BookKeeper。BookKeeper 支持仅追加/不可变数据存储,采用高可复制的分布式日志,满足我们对系统 CAP 的要求。BookKeeper 还具备以下特点:
Salesforce 也一直支持开源产品,Apache BookKeeper 社区积极活跃,充满活力。
Apache BookKeeper 几乎实现了我们对存储系统的全部要求,但仍需做一些工作。首先来看一下 Apache BookKeeper 可以实现我们的哪些要求。
从持久性来看,Ledger 跨 Bookie Ensemble 复制,Ledger 内的 Entry 可以跨 Ensemble。
Ensemble Size: 5 Write Quorum Size: 3 Ack Quorum Size: 2
写入根据 Write Quorum 和 Ack Quorum(可配置)进行确认,从而保证低写入延迟和高可扩展。
但实际上,在云中的商用硬件上运行 BookKeeper 并不轻松。
数据布局策略不具备云感知能力,并且没有顾及底层云服务提供商(云基础设施)。目前,一些用户的部署方法是手动标识不同可用性区域中的节点,并进行逻辑分组,然后以组为单位改进数据布局策略。这不失为一种解决方案,但不支持区域故障,也降低了维护和升级大型集群时系统的易用性。
另外,所有云基础设施的可用区域里都出现过停机情况;而一般的理解是,应用程序要针对这些故障做相应的设计。一个很好的例子是,2012 年圣诞节期间,Amazon 网络服务可用区域故障,Netflix 底层所依赖的公有云基础设施停机,而 Netflix 服务仍然可以在有限的容量上运行。
公有云基础设施易于扩展,在一定程度上降低了使用和维护的成本,因此,从网站到应用程序,甚至是企业级软件,基本都在公有云服务提供商提供的基础设施上运行。但是,公有云也有其缺陷,它在节点、区域或地区层面都可能出现不可用的情况。底层基础设施不可用,用户什么都做不了。起因可能是某些机器、区域或地区出现故障,也可能是由硬件故障引起的网络延迟增加。所以最终当在公有云基础设施上运行应用程序时,开发人员在设计时需要考虑由故障引发的问题。
Apache BookKeeper 本身不能解决这一问题,因此,我们需要自行设计一个修复程序。
对问题有了一定的了解之后,我们开始考虑解决方案,让 BookKeeper 具备云感知能力,满足我们的以下要求。
我们的解决方案如下。
现有的 BookKeeper 架构为所有 Bookie 提供唯一标识(在首次启动时分配)。标识存储在元数据存储(ZooKeeper)中,其他 Bookie 或客户端可以访问。
使 Apache BookKeeper 具有云感知能力的第一步是让所有 Bookie 均可获取它部署在 Kubernetes 集群中的位置。 我们认为 Cookie 数据是获取位置信息的最佳方式。
因此,我们在 Cookie 中增加了 networkLocation 字段,它包含两部分:可用区域和升级域,用于定位 Bookie。Kubernetes 和云基础设施无关,我们可以使用 Kubernetes API 来查询底层的可用区域信息。我们还根据涉及主机名顺序索引的公式生成了 upgradeDomain 字段。它可以用来滚动升级,而不影响集群的可用性。
在机器启动时生成上述字段和对应值,并保存在元数据存储中供用户访问。这些信息可以用于生成 Ensemble,分配 Bookie 到 Ensemble,以及确定从哪些 Bookie 复制数据,复制的数据存储到哪些 Bookie。
现在,客户端已经足够智能,可以与某些区域中的 Bookie 进行通信,下一步便是确保有一个可以使用这一信息的数据布局策略。我们开发了 ZoneAwareEnsemblePlacementPolicy(ZEPP)。这是一个针对基于云部署而设计的两级层次化布局策略。ZEPP 可以获取可用区(AZ)和 upgradeDomains(UD)信息。
AZ 是区域内隔离数据中心的逻辑概念;UD 是 AZ 内的一组节点,关闭 UD 不会影响服务,UD 还可以监测到区域的关闭和重启。
下图为 ZEPP 可采用的一种部署示意图。这种部署方式兼顾了 Cookie 中的 AZ 和 UD 信息,并据此对 Bookie 节点进行分组。
进行上述调整后,Apache BookKeeper 可以具备云感知能力了。但成本也是设计架构时必须考虑的因素之一。大多数云基础设施对传出服务的数据进行单向收费,跨可用区传输的费用会有所不同。这是 BookKeeper 客户端需要考虑的一个重要因素,因为它现在是从 Ensemble 中随机选择一个 Bookie 进行读取。
如果 Bookie 和客户端属于不同的可用区,会增加不必要的成本。数据复制可能发生在跨可用区的 Bookie 之间,当可用区出现故障时,使用成本会增加。
我们通过以下方式来处理这些特殊情况。
目前,BookKeeper 客户端从 Ensemble 随机选取 Bookie 进行读取。借助重排序读取特性,现在客户端可以选择 Bookie,从而减小读延迟,降低成本。
启用重排序读取后,客户端按照以下顺序选择 Bookie:
按照上述顺序,运行很长时间且出现过故障的系统也可以满足我们对延迟与成本的要求。
当区域关闭时,不同 Ensemble 中的所有 Bookie 都开始将数据复制到当前可用区域内的 Bookie 中,从而满足 Ensemble Size 和 Quorum 要求,引起“惊群问题”。
要解决这一问题,首先要确定区域关闭的时间。故障可能是暂时性的操作失误,比如网络故障引起区域不可用,我们不希望系统复制 TB 级的数据;但同时我们也要做好准备,应对真正的故障。我们的解决方案包含两步:
下图为区域关闭与重启时我们的应对方案。
根据区域中可用 Bookie 数量和区域中 Bookie 总量可以计算出 HighWaterMark 和 LowWaterMark 的值。用户可以为这两个值设置阈值,系统可以据此判断故障情况,进而确定故障类型。
当区域标记为关闭时,我们会禁用自动复制,从而避免跨区域自动复制 TB 级的数据。此外,我们在数据复制的地方增加了告警,提示用户可能出现的区域故障。我们认为,运维专家能够将噪声与实际故障区分开,并决定是否开始自动复制整个区域的数据。
我们还可以通过 shell 命令启动已禁用的 Bookie 自动复制。
Apache BookKeeper 是一个开源项目,社区非常活跃,并一直在积极讨论面临的一系列挑战。由于 BookKeeper 是存储数据的组件,对很多用户而言,其云感知能力十分重要。
本文介绍的更改已在 Salesforce 进行了实战验证。目前,借助 Apache BookKeeper ,我们已经可以支持 AZ 和 AZ + 1 故障。但是,这样的架构更改必然会影响到可用性、延迟、成本、部署和维护的简易性。社区已经接受了我们提交的一些更改,我们会继续为社区做出贡献。我们希望这些更改可以简化集群打补丁、升级、重启的操作,同时尽可能降低对消费服务的影响。
Anup Ghatage 任职于 Salesforce,主要负责云基础架构和数据工程,曾任职于 SAP 和 Cisco Systems,对维护和开发高可扩展的系统有浓厚兴趣。他本科毕业于普纳大学计算机专业,硕士毕业于卡耐基·梅隆大学。他是 Apache BookKeeper 的 committer,积极参与 Apache BookKeeper 的开发。欢迎在 Twitter 上关注 Anup(@ghatageanup )。
原文链接:
https://www.infoq.com/articles/storage-cloud-apache-bookkeeper/
领取专属 10元无门槛券
私享最新 技术干货