多年来,随着我们在规模和功能上的扩展,Facebook 已经从单一基本的 web 服务器架构演进成一个包含数千个在后台工作的服务的复杂架构。扩展 Facebook 产品所需的各种后端服务并不是一件小事。而且,我们发现我们许多团队都在构建自己的具有重叠功能的定制化分片解决方案。为解决这个问题,我们将 Shard Manager 构建为一个通用平台,它能促进可靠的分片应用程序的高效开发和运维。
事实上,用分片来扩展服务的概念并不新鲜。然而,据悉,我们是业界唯一在我们的规模内得到广泛采用的通用分片平台。Shard Manager 管理着成百上千万的分片,这些分片托管在成百上千个服务器上,覆盖数百个线上应用程序。
分片
在最基本的形式中,人们熟悉分片是它作为一种扩展服务的方法来支持高吞吐量。下图展示了一个典型的 web 技术栈的扩展。其中,web 层通常是无状态的,并且易于扩展。由于任何服务器都可以处理任何请求,因此可以使用很多种流量路由策略,例如循环策略或随机策略。
Facebook 的应用程序栈
另一方面,由于数据库部分是有状态的,因此对其进行扩展不容易。我们需要使用一种方案来确定地在服务器之间传播数据。像hash(data_key) % num_servers
这样的简单哈希方案可以传播数据,但是新增服务器时会存在数据混乱的问题。一致性哈希方案通过将一小部分数据从现有服务器重新分发到新服务器来解决这个问题。
然而,这个方案要求应用程序具有细粒度的秘钥,以便有效地进行统计负载均衡。一致性哈希支持基于约束的分配(例如,欧盟用户的数据应该存储在欧洲数据中心以降低延迟)的能力也因其这一天然属性而受到限制。因此,只有某些应用程序(如分布式缓存)才采用这种方案。
一种备选方案是显式地将数据划分到分配在各服务器的分片。数十亿用户的数据存储在多个数据库实例中,每个实例都可以看作一个分片。为提高容错性,每个数据库分片可以有多个拷贝(也称为副本),每一个副本能根据一致性要求扮演不同的角色(例如,主副本或次副本)。
分片到服务器的分配,是针对协调各种约束的能力(例如局部性偏好)进行显式计算过的,而哈希解决方案没法支持这些约束。我们发现,分片方案比哈希方案更灵活,适合更广泛的分布式应用程序的需要。
采用这种分片方案的应用程序通常需要一定的分片管理能力,才能可靠地进行大规模运维。最基本的能力是故障转移能力。在发生硬件或软件故障时,系统可以将客户端流量从故障的服务器转移出去,甚至可能需要在正常服务器上重建受影响的副本。在大型数据中心,通常有计划的服务器停机时间来执行硬件或软件维护。分片管理系统需要确保每个分片都有足够的健康副本,这通过主动将副本从有必要关闭的服务器转移出去来实现。
另外,可能不均衡和不断变化的分片负载需要负载均衡,这意味着每个服务器托管的分片必须动态调整,以实现统一的资源利用,提高整体的资源效率和服务可靠性。最后,客户端流量的波动需要分片扩展,系统根据每个分片动态调整副本因子,以确保其平均每个副本的负载保持最佳。
我们发现,Facebook 不同服务的团队已经在构建自己的定制化解决方案,其完整性程度各不相同。能处理故障转移的服务比较常见,但很少有负载均衡能力。这导致可靠性不能达到最优效果和较高的运维开销。这就是为什么我们要将 Shard Manager 设计为通用的分片管理平台。
使用 Shard Manager 作为平台分片
多年来,已有数百个分片应用程序被构建或迁移到 Shard Manager。经过长期的快速发展,有上千万分片副本分配在成百上千万台服务器上,如下图所示。
这些应用程序协助各种面向用户的产品的顺利运行,包括 Facebook app、Messenger、WhatsApp 和 Instagram。
应用服务器总数量的增长
除了数量庞大的应用程序外,它们的用例在复杂度和规模上都有显著不同,从简单的拥有几十台服务器的柜台服务,到拥有数万台服务器的复杂的基于 Paxos 的全局存储服务。
下图展示了代表性应用程序的范围,用字体大小表明它们的规模。
Shard Manager 上的代表性应用程序
各种因素促成了广泛采纳。首先,与 Shard Manager 集成意味着简单地实现一个由add_shard
和drop_shard
原始命令组成的小接口。其次,每个应用程序都可以通过基于 intent 的规范来声明其可靠性和效率要求。第三,通用约束优化求解器的应用让 Shard Manager 能提供多功能的负载均衡功能,并轻松添加对新均衡策略的支持。
最后值得一提的是,通过完全集成到整个基础设施生态系统中,包括容量和容器管理,Shard Manager 不仅支持分片应用程序的高效开发,而且还支持安全运维,这是没有相似平台提供的端到端解决方案。Shard Manager 比类似平台(例如 Apache Helix)支持更复杂的用例,包括基于 Paxos 的存储系统用例。
Shard Manager 应用程序的类型
我们从 Shard Manager 上的应用程序中抽取出一些共性,并将它们分为以下三类:只有主副本、只有次副本、兼具主副本和次副本。
只有主副本:
每个分片只有单个副本,称为主副本。这种类型的应用程序通常将状态存储在外部系统中,例如存储在数据库和数据仓库中。一个常见的范例是,每个分片代表一个工人,获取指定的数据,处理他们,选择性的响应客户端请求,并通过可选的优化手段(例如批处理)来写回结果。
流处理是一个真实例子,从一个输入流中处理数据并将结果写入到一个输出流中。Shard Manager 提供了一个“最多一个主副本”的保证来帮助避免由于数据重复处理导致的数据不一致,就像传统的 ZooKeeper 基于锁的方法一样。
只有次副本:
每个分片都有多个角色相同的副本,称为次副本。多个副本的冗余性提供了更好的容错性。
此外,还能根据工作负载调整副本因子:热门分片可以有更多副本来分散负载。通常,这种类型的应用程序是只读的,没有很强的一致性要求。它们从外部存储系统获取数据,有选择地处理数据,本地缓存结果,并根据本地数据响应查询。
一个实际的例子是机器学习推理系统,它从远程存储器下载训练好的模型并响应推理请求。
兼具主副本和次副本:
每个分片都有两种角色的多个副本——主副本和次副本。这些类型的应用程序通常是对数据一致性和持久性有严格要求的存储系统,其中主副本接受写入请求并驱动所有副本之间的复制,次副本提供了冗余性并可以选择性地响应读取请求来减少主副本上的负载。其中一个例子是 ZippyDB,这是一个基于 Paxos 副本的全局键值存储系统。
我们发现,以上三中类型能代表大部分 Facebook 的分片应用程序。截至 2020 年 8 月的百分比分布,如下图所示:67%的应用程序是只有主副本的,这是由于架构的简单性以及与传统 ZooKeeper 基于锁的解决方案在概念上的相似性。
然而,就服务器数量而言,只有主副本的为 17%,这意味着只有主副本的应用程序平均比其它两种类型的应用程序小。
截至 2020 年 8 月的应用程序数量和服务器数量百分比分布
使用 Shard Manager 构建应用程序
在应用程序所有人决定如何将他们的工作负载或数据分割到分片中以及哪种应用程序类型适合他们的需求后,有三个简单直接的标准化步骤可以在 Shard Manager 上构建一个分片应用程序,无论是哪种用例。
应用程序连接 Shard Manager 库,然后实现分片状态转换接口,同时插入他们的业务逻辑。
应用程序所有者提供基于 intent 的规范来配置约束。Shard Manager 提供了 4 个开箱即用的主要功能:容错、负载均衡、分片扩展和安全运维。
应用程序客户端使用一个通用的路由库来路由分片请求。
分片状态转换接口
我们的分片状态转换接口由一组如下所示的短小精炼的原始命令组成,通过这些原始命令插入特定的应用程序逻辑:
add_shard
调用指示一个服务器加载由传入的分片 ID 标识的分片。返回值标识转换的状态,例如分片加载是否在进行中或者运行出错。相反地,drop_shard
调用指示一个服务器抛弃某个分片并停止响应客户端请求。
这个接口给予应用程序完全的自由,来将分片映射到它们特定域的数据。对于存储服务,add_shard
调用通常触发结点副本的数据传输;对于一个机器学习推理平台,add_shard
调用触发模型从远程存储加载到本地主机。
基于以上原始命令,Shard Manager 构建了一个高级的分片转移协议,如下图所示。Shard Manager 决定将一个分片从高负载的服务器 A 转移到一个负载相对较轻的服务器 B,从而实现负载均衡。首先,Shard Manager 向服务器 A 发出一个drop_shard
调用并等待它成功完成。然后,它向服务器 B 发出一个add_shard
调用。这个协议提供了最多一个主副本的保证。
分片跨服务器转移
以上两个基本原始命令是典型应用程序变得切片化并实现伸缩扩展所需的全部内容。对于复杂的应用程序,Shard Manager 支持更强大的接口,下面将详细介绍这些接口。
在上述协议中,处于转移过程中的分片的客户端在分片不在任何服务器上的那段时间内会经历短暂的不可用,而这对于面向用户的应用程序来说,这是不可接受的。因此,我们开发了一个更完善的协议,支持无缝的所有权移交并最大限度地减少分片的停机时间。
对于兼具主副本和次副本的应用程序,提供了两种传统的原始命令,如下所示:
change_role
用来转换副本的主次角色。update_membership
指示一个分片的主副本验证和执行副本成员关系的变更,这对于基于 Paxos 的应用程序最大化数据正确性非常重要。
以上接口是我们深入分析和处理分片应用程序经验的结果。结果证明它们足够通用,可以支持大部分应用程序。
各种功能基于 intent 的规范
容错能力
对于分布式系统,故障是常事而非异常,而知道如何准备和从故障中恢复,这对于实现高可用性是至关重要的。
副本:通过副本实现冗余,这是提升容错能力的一种常见策略。Shard Manager 支持在每个分片基础上配置副本因子。如果单个容错域的故障可以关闭所有冗余副本,那么副本的好处是微乎其微的。Shard Manager 支持跨可配置的容错域(例如,用于区域应用程序的数据中心建筑和用于全球应用程序的区域)传播副本。
自动故障检测和分片故障转移:Shard Manager 能够自动化检测服务器故障和网络隔断。在检测到一个故障后,立即构建替代副本并不总是理想的。Shard Manager 通过配置故障检测延迟和分片故障转移延迟,让应用程序能在构建新副本的开销与可接受的不可用性之间做出适当的权衡。
此外,当网络隔断发生时,应用程序可以在可用性和一致性之间做选择。
故障转移限流:为了防止级联故障,Shard Manager 支持故障转移限流,它限制了分片故障转移的频率,并保护其它正常服务器在重大停机情况下不会突然过载。
负载均衡
负载均衡是指在一个连续的基准上将分片及其工作负载均匀地分布在应用服务器上的过程。它可以有效利用资源并避免热点。
异构硬件和分片:在 Facebook,我们有多种类型和代际的硬件。大部分应用程序需要运行在异构硬件上。由于应用程序的工作负载或数据不能均匀地分片,因此分片的大小和负载会有所不同。Shard Manager 的负载均衡算法考虑了每台服务器和每个分片(副本)的细粒度信息,因此支持异构硬件和分片。
动态负载收集:一个分片的负载会在使用中随着时间而变化。如果应用程序的可用容量与动态资源(比如可用磁盘空间)绑定,那么它可能会有所不同。Shard Manager 定期从应用程序收集每个分片的负载和每个服务器的容量,并进行负载均衡。
多资源均衡:根据用户配置不同的优先级,Shard Manager 支持同时平衡多种资源,如计算、内存和存储。这保证了瓶颈资源的利用率在可接受的范围内,并尽最大可能平衡非关键资源的使用。
限流:与故障转移限流类似,负载均衡生成的分片移动的数量在总移动数粒度和每个服务器的移动数粒度上进行限流。
上述对空间和时间负载变化的多功能支持满足了分片应用程序的不同平衡需求。
分片扩展
Facebook 的许多应用程序响应直接或间接来自用户请求。因此,流量呈现出一种日间模式,在高峰期和非高峰期之间,请求频率显著下降。
弹性计算,基于工作负载的变化动态调整资源分配,是一种不需要牺牲可靠性就能提升资源效率的解决方案。为了响应实时负载变化,Shard Manager 可以执行分片扩展,这意味着当一个分片的平均每个副本的负载偏离了用户配置的可接受范围,它能动态调整副本因子。分片扩展限流可以配置在给定期限内新增或废弃的副本数量。
下图展示了一个分片的扩展过程。最初,所有副本的总负载增加,每个副本的负载增加。一旦每个副本的负载超过了阈值上限,分片扩展就会开始,并添加足够数目的新副本来使每个副本的负载回到一个可接受的范围。稍后,分片负载开始减少,那么分片扩展会减少副本的数量来释放不需要的资源,以供其它热点分片或应用程序使用。
分片扩展过程的图示
安全运维
除了故障,运维事件也是常态而不是异常,要被视为头号问题来尽量减少它们对可靠性的影响。常见的运维事件包括字节码更新、硬件修复和维护以及内核升级。
Shard Manager 与容器管理系统Twine进行了合作设计,以实现无缝事件处理。Twine 聚集事件,将它们转换成容器生命周期事件,例如容器停止/重启/移动,并且通过TaskControl接口将它们通信给 Shard ManagerScheduler。
Shard Manager Scheduler 评估事件的破坏性和长度,并采取必要的主动分片移动来防止事件影响可靠性。Shard Manager 保证每个分片必须拥有至少一个健康的副本。
对于具有多数法定人数规则的基于 Paxos 的应用程序,Shard Manager 支持另一种保证,即保证大多数副本是健康的。运维安全与效率之间的权衡是随着应用程序变化的,而且可以通过配置调整,例如同时受影响的分片上限。
下图展示了一个应用程序的例子,包含 4 个容器和 3 个分片。首先,一个短期的维护操作(例如内核升级或者影响容器 4 的安全补丁请求),Shard Manager 允许操作立即进行,因为所有的分片在其它服务器上还有其余副本。接下来,对容器 1 到容器 3 请求二进制更新。由于并行更新任何两个容器都会导致分片不可用,因此 Shard Manager 串行更新这些容器,即每次只更新一个。
运维事件处理的一个例子
客户端请求路由
我们使用了一个通用的路由库来路由 Facebook 的请求。这个路由库使用了一个应用程序的名字和分片 ID 作为输入,返回一个 RPC 客户端对象,通过该对象可以简单地进行 RPC 调用,如下面的代码所示。发现分片的分配位置的秘诀被隐藏在create_rpc_client
。
Shard Manager 的设计和实现
在本节,我们将深入介绍 Shard Manager 是如何被设计,用来支持我们所讨论的那些功能。我们将从基础设施层次开始分享,特别是 Shard Manager 的角色。
基础设施栈的层次
在 Facebook,我们的整体基础设施是用一种分层的方案构建的,各层次之间的关注点明显分离。这让我们能独立而稳健地演进和扩展每一层。
下图展示了我们基础设施的层次。每一层分配和定义了相邻上层操作的范围。
基础设施栈
主机管理:资源管理系统(Resource Allowance System)管理着所有的物理服务器,然后给各个组织和团队分配定量资源。
容器管理:Twine 从资源管理系统获取资源,将它分配给容器单元内的单独的应用程序。
分片管理:对于分片的应用程序,Shard Manager 用 Twine 提供的容器分配分片。
分片的应用程序:在每个分片中,应用程序分配并运行相关的工作负载。
产品:这些都是面向用户的产品,例如移动应用程序,由分片的后端应用程序支撑。
除了每层对相邻的较低层的向下功能依赖,整个基础设施栈是通过向上传播的信号和事件进行联合设计和协同工作的。特别是对于 Shard Manager 层, TaskControl是我们实现协同调度的机制。
设计
中央控制面板
Shard Manager 是一个纯粹的控制面板服务,监控应用程序状态并协调应用程序的数据在跨服务器不同分片上的移动。集中式的全局视图让 Shard Manager 能计算全局最优的分片分配,并通过整体协调所有计划的运维事件来保证高可用性。在这个中央控制面板关闭的情况下,应用程序可以使用现有的分片分配继续以降级模式运行。
拥有状态转换接口的不透明分片
对 Shard Manager 来说,分片是不透明的,用户可以在他们的应用程序中将它映射成任何实体,例如数据库实例、日志集和数据集。我们定义了每个应用程序都必须实现的分片状态转换接口。
这种清晰的划定让 Shard Manager 与特定于应用程序的数据面板区分开来,并在可以利用 Shard Manager 的用例方面提供巨大的灵活性。
分片最佳粒度
分片粒度是很重要的。太粗糙的粒度会导致负载均衡较差,而太精细的粒度会导致对底层设施不必要的管理负担。我们特意选择了一个最佳选择,即为每个应用程序服务器分配数百个分片,并在负载均衡质量和基础设施成本上达到很好的平衡。
通用约束优化
用例多样性的一个表现形式就是应用程序希望通过分配分片来实现容错性和效率的各种方法。我们采用了一个通用约束优化求解器来实现可扩展性。
当增加对新需求的支持时,Shard Manager 只需要在内部将它描述为约束,并将它们输入到求解器中就能计算出最佳的分片分配,而我们的代码库几乎没有增加复杂性。
架构
这里展示了 Shard Manager 的架构,包括如下所示的各种组件。
Shard Manager 的架构
应用程序所有者会向 Shard Manager Scheduler 提供一份规范,包含管理应用程序所需的所有信息。
Shard Manager Scheduler 是协调分片转换和移动的中心服务。它收集应用程序状态;监控状态变化,例如服务器加入、服务器故障以及负载变化;调整分片分配;通过对应用服务器的 RPC 调用来驱动分片状态转换。Shard Manager Scheduler 内部进行分片从而实现水平扩展。
应用程序连接 Shard Manager 库,这个库通过连接 ZooKeeper 提供服务器成员信息和活动状态检查。应用程序实现分片状态转换接口,并由 Shard Manager Scheduler 指示进行状态转换。应用程序可以测量和公开由 Shard Manager Scheduler 收集的动态负载信息。
Shard Manager Scheduler 将分片分配的公共视图发布到一个高度可用和可伸缩的服务发现系统,该系统将信息传播到应用程序客户端,以便对请求进行路由。
应用程序客户端连接一个通用路由库,该库以每个分片为基础封装了服务器端点发现信息。在端点发现后,客户端请求被直接发送到应用服务器。因此,Shard Manager Scheduler 不在请求相应的关键路径上。
总结
Shard Manager 为构建分片应用程序提供了一个通用平台。用户只需要实现一个分片状态转换接口并通过基于 intent 的规范来表达分片约束。这个平台与 Facebook 生态系统的其余部分完全集成,这将底层基础设施的复杂性隐藏在一个整体合同背后,并让我们的工程师能聚焦于应用程序和产品的核心业务逻辑。
Shard Manager 从九年前开始的时候就一直在演进,但这一过程还远未完成。我们将继续努力为 Facebook 构建分片服务提供一流的解决方案。
尽管取得了一些成功,但是我们仍在多个方面扩展 Shard Manager 的规模和功能。以下是我们计划在未来几年应对的挑战:
通过将应用程序在内部划分为较小的独立分区,支持每个应用程序数千万个分片来满足不断增长的大型应用程序的需求。
通过为用户定制化提供更高程度的模块化和插件化的同时,使 Shard Manager 保持简单,来支持更复杂的应用程序。
为当前抽象太过繁重的小型简单应用程序简化试用体验。
原文链接:
https://engineering.fb.com/production-engineering/scaling-services-with-shard-manager
评论