作者 | Leonardo Creed
译者 | 平川
策划 | Tina
本文最初发布于 Engineer’s Codex 博客。
Meta 的无服务器平台 XFaaS“每天要处理来自数十个数据中心区域的 10 万多台服务器上的数万亿次函数调用。”
XFaaS 是 Meta 内部的函数即服务(FaaS),类似于 AWS Lambda、Google Cloud Functions 和 Azure Functions 等公共 FaaS 选项。
我有幸提前读到了他们就此撰写的论文。
在命名方面,计算机科学家并不是很有创意,因为今年就有两篇论文介绍“XFaaS”。一篇来自 Meta,另一篇来自 ISC Bangalore 的科学家。 两篇论文介绍了两个完全不同的系统,因此,如果用谷歌搜索“XFaaS”,则需要知道,本文讨论的是该系统的 Meta 版本。
在这篇文章中,我将首先介绍要点内容和相关的经验教训,供那些想要了解大概情况的读者参考。
然后,我将为那些想要深入理解 XFaaS 架构的读者做更详细的讲解。
有趣的数据和结论
- 本文的一个重点是可以通过软件来优化硬件利用率,从而提高无服务器的性能。
- Meta 认识到,无服务器函数的启动开销存在浪费,因此,他们希望通过模拟一个通用 worker 来消除这种浪费,即任何 worker 都可以立即执行任何函数而无需启动开销。
- 在这么大的规模下,硬件成本非常高,每一个百分点都至关重要。
- XFaaS 仅用于非面向用户的函数。无服务器函数的延迟变化幅度比较大,无法提供稳定的面向用户的函数。
- XFaaS 客户端提交的函数调用请求存在很大的波动。峰值需求是非峰值需求的 4.3 倍还多。
- 他们提供的一个负载示例在 15 分钟内向 XFaaS 提交了 2000 万个函数调用请求。
- Meta 发现,尖峰函数的调用有其模式,借此,他们设法使工作负载中的尖峰函数更可预测。
XFaaS 的效率如何?
XFaaS 日均 CPU 利用率为 66%,远远优于行业平均水平。
不同区域中 worker 的 CPU 利用率
它利用时间(通过延迟函数调用)和空间(通过将其发送到负载较少的数据中心)有效地分散了负载。
Meta 正在将它们的许多函数安排到非高峰时段,这样负载和成本更可预测。
因为是内部云,所以 Meta 可以执行许多独有的优化,例如,在同一进程中运行来自不同用户的多个函数。
大多数函数不用一秒就可以完成,但并非全部如此。
XFaaS 解决的问题
问题:漫长的冷启动时间
- 如果容器过早关闭,则下一次调用时就不得不再次初始化整个容器。
- 如果容器关闭得太晚,它就会处于闲置状态,浪费宝贵的计算资源。
- 解决方案:XFaaS 使用即时编译之类的方法来近似地实现每个 worker 都可以立即执行任何函数的效果。
问题:负载变化幅度大
- 资源配置过度导致硬件成本增加,或者资源配置不足导致系统速度较慢。
- 解决方案:XFaaS 将延迟容忍度低的函数推迟到非高峰时段运行,并将函数调用分散到全球各个数据中心区域。
问题:导致下游服务过载
- 例如有一次,来自非面向用户函数的调用激增,导致面向用户的在线服务中断。
- 解决方案:XFaaS 采用类似于 TCP 拥塞控制的机制来调节函数的执行。
与公有 FaaS(AWS Lambda、Google Cloud Functions、Azure Functions)比较
- 公有云 FaaS 会将函数执行限制在单个数据中心区域,而 XFaaS 可以全局调度函数调用,实现更好的负载平衡。
- FaaS 平台首要关注的是减少延迟,而忽略了硬件利用率。XFaaS 关注的则是硬件利用率和函数调用的吞吐量。
可供公有 FaaS 借鉴的经验
以下几项 XFaaS 技术可能会对公有云有所助益:
- 允许调用者指定函数执行的开始时间。
- 允许函数所有者根据完成期限设置服务水平目标(SLO)(SLO 低则可以延迟到更好的时间段执行)。
- 允许函数所有者为函数设置紧急度。
虽然公有云可能不会像 XFaaS 那样在同一个进程中运行来自不同用户的函数,但大型云客户可以在其虚拟私有云中采用 XFaaS 方法。
少数几个 Meta 团队消耗了 XFaaS 很大一部分的容量。类似地,公有云中的大客户或许也可以受益于 XFaaS 的策略。
背景:前提和要求
从这里开始,我将详细介绍 XFaaS。
正如前面提到的,XFaaS 需要应对尖峰负载。在需求高峰期,仅一个函数每分钟就能收到 130 万个调用请求。
前提
这里有一个关键点是,大多数 XFaaS 函数都是由自动化工作流触发的,可以接受延迟。前面已经说过,这使得 XFaaS 可以从时间(通过延迟函数的执行)和空间(通过将其发送到负载较少的数据中心)两个角度来平衡负载。
XFaaS 支持 PHP、Python、Erlang、Haskell 运行时,以及一个适用于任何语言的基于容器的通用运行时。
函数的生命周期
函数有几个开发者可以设置的属性,如函数名、参数、运行时、紧急度、执行开始时间、执行完成期限、资源配额、并发限制和重试策略。执行完成期限的范围从几秒到 24 小时不等。
最后,XFaaS 在不同区域的硬件能力并不相同,因此负载平衡必须要考虑到这一点。
XFaaS 在不同数据中心区域的硬件容量并不均匀
工作负载类型
在 XFaaS 上,Meta 有三种类型的工作负载:队列触发的函数、事件触发的函数(来自数据仓库和数据流系统中的数据更改事件)和计时器触发的函数(在预先设定好的时间自动触发)。
函数分类
XFaaS 针对的是非面向用户的函数,例如异步推荐系统、日志记录、生产力机器人、通知等等。
整体架构
XFaaS 架构概览
客户端通过向提交者发送请求来发起函数调用,提交者在将请求传递给 QueueLB(队列负载均衡器)时会施加速率限制。 QueueLB 将函数调用保存到 DurableQ,在调用完成之前它会一直保存在那里。 调度器会定期将这些调用移动到它的 FuncBuffer(内存中的函数缓冲区)中,并按紧急度、完成期限和资源配额对它们进行排序。 准备执行的调用会被移入 RunQ,然后 WorkerLB(工作负载均衡器)会将它们分配给合适的 worker。
提交者、QueueLB、调度器和 WorkerLB 都是无状态、不分片的,并且复制时不指定领导者,因此,它们的副本都扮演相同的角色。
DurableQ 是有状态的,它有一个分片的、高可用性的数据库。
无状态组件很容易扩展,只需要添加更多的副本即可。如果一个无状态组件出现故障,它的工作可以直接由同级组件接管。
中央控制器和容错机制
中央控制器与函数执行路径是分开的。它们通过更新关键配置参数不断优化系统。所谓关键配置参数是指那些供关键路径组件(如 worker 和调度器)使用的参数。
这些配置会缓存在组件中,所以如果中央控制器出现故障,系统仍能正常运行(只是不能重新配置了)。
这样,中央控制器停机 10 几分钟也不会导致什么问题。这是 Meta 构建系统弹性的一个例子。
数据隔离
如果函数出于安全性或性能考虑需要强隔离,那么它就会被分配到不同的命名空间。每个命名空间使用不同的工作者进程池来实现物理隔离。
由于对私有云的信任、强制性同行评审和现有的安全措施,多个函数可以在单个 Linux 进程中运行。数据只能从较低的分类级别流向较高的分类级别。
提交者
客户端向提交者发送调用请求。为了提高效率,提交者会批量处理这些调用请求,并通过一个操作写入 DurableQ。
提交者通过一个分布式键值存储来实现大型参数存储,并内置了速率限制策略。为了处理客户端提交速率的变化,区域为常规客户端和高频客户端设置了两个提交者集合。
XFaaS 会主动监视高频客户端,而限流或服务水平目标(Service Level Objective,SLO)的调节则需要人工干预。
QueueLB 和 DurableQ
然后,提交者向 QueueLB 发送函数调用请求。配置器(配置管理系统中央控制器)会根据 DurableQ 硬件容量的变化向 QueueLB 提供路由策略,以平衡不同区域的负载。
发送到 DurableQ 的调用请求会通过 UUID 进行分片,从而实现均匀分布。每个 DurableQ 根据调用者设置的计划执行时间对函数调用进行分类和存储。
调度器会不断地查询 DurableQ,从存储的函数调用中查找到期的。当 DurableQ 将一个函数调用传递给调度器时,除非存在执行失败的情况,否则它对调度器而言就是唯一的。
调度器与 DurableQ 的通信:
- 执行成功时发送一条 ACK 消息。然后函数调用就会从 DurableQ 中永久删除。
- 执行不成功发送一条 NACK 消息。该函数调用会重新出现在 DurableQ 中,由另一个调度器处理。
调度器
调度器的主要作用是根据函数调用的重要性、截止日期和容量配额来确定它们的优先级。
它从 DurableQ 中获取函数调用,将它们合并到 FuncBuffer 中(按重要性排序,然后是执行截止日期),然后将它们安排到 RunQ 中执行。FuncBuffers 和 RunQ 都是内存中的数据结构。
为了实现高效执行及防止系统延迟,RunQ 会对函数调用流进行控制。该系统还会保证负载均衡,Global Traffic Conductor(中央控制器)会根据需求和容量跨区域路由函数调用流,全面优化 worker 利用率。
WorkerLB 和 Worker
调度器的 RunQ 将函数发送到 WorkerLB(工作负载均衡器,在工作者进程池中运行函数)。
在 XFaaS 系统中,使用相同编程语言的函数是相互隔离性的,有专用的运行时和工作者进程池。
该系统的设计旨是使任何 worker 都能立即执行函数,而不会出现任何初始化延迟。XFaaS 会维护一个始终活跃的运行时,并保持本地 SSD 上的函数代码最新。
通过协作式即时(JIT)编译减少开销
XFaaS 引入了协作式 JIT 编译,定期将函数代码打包推送给工作者,之所以要这样做是因为代码经常更新。
JIT 编译有以下三个执行阶段:
- 由几个 worker 测试新代码;
- 由 2% 的 worker 进一步测试代码;有些执行 JIT 编译性能分析;
- JIT 是在接收到函数调用请求之前完成,消除了延迟。
利用协作式 JIT 编译,worker 在函数代码更新后只需 3 分钟就能达到每秒最大请求数,而不使用 JIT 编译则需要 21 分钟。
通过本地分组提高内存利用效率
因为存在内存限制,所以存储每个函数的 JIT 代码并不可行。
Locality Optimizer(中央控制器)将函数和 worker 划分为组,确保需要大量内存的函数是分布式执行的。
使用本地分组减少了 11~12% 的内存消耗,并且可以保证 worker 高效、一致地使用内存。
工作者进程的内存利用率
XFaaS 如何有效地处理负载峰值
- 函数资源配额:每个函数都有一个配额,由其所有者设置,该配额定义了它每秒的 CPU 周期。该配额会转换为每秒请求数(RPS)速率限制。中央速率限制器会根据函数的 RPS 限制确定是否限制函数调用。
- 时移计算:XFaaS 提供保留配额(用于快速执行)和机会配额(用于 24 小时内在低需求期间执行)。Utilization Controller 根据工作者利用率动态调整函数调用的速率。
- 函数动态 RPS 限制:为了防止下游服务负担过重,XFaaS 使用类似 TCP 的 AIMD(Additive Increase, Multiplicative Decrease)方法动态调整 RPS 限制。它可以设置并发级别,并使用慢启动方法管理 RPS 转移。
我们过去遇到的一些挑战,如 XFaaS 函数使 TAO 数据库过载导致服务级联故障,突显了这些保障措施的必要性。现在,XFaaS 已经证明了其保护下游服务的能力,正如在真实事件中所看到的那样,它的背压机制提前抑制了潜在的过载,确保了服务的稳定性。
一己之见
这是一篇很棒的论文。Meta 向我们详细介绍了他们的无服务器平台,并为想要优化无服务器函数使用方法的开发人员和公司提供了可供借鉴的经验教训。阅读完整论文,请点击这里(可能需要机构访问权限才能免费阅读)。
此外,使用软件优化硬件(例如 CPU 使用效率)在业界还没有得到足够的重视。虽然谷歌、Facebook 等公司针对自己的系统做了这样的工作,但与软件优化相比,人们对于这个话题的讨论并不算多。
原文链接:
https://engineercodex.substack.com/p/meta-xfaas-serverless-functions-explained
声明:本文为 InfoQ 翻译,未经许可禁止转载。