XFaaS: Hyperscale and Low Cost Serverless Functions at Meta

主要介绍了Meta在FaaS上、站在provider角度的优化。 具体包括,消除函数的冷启动时间、在不过度配置资源的情况下处理负载峰值、防止函数对下游服务造成过载。

(读系统文章的时候发现还是适合机翻,因为细节太多了)

FaaS 对用户的易用性可能是以云提供商额外的硬件成本为代价的。 在使用 FaaS 之前,用户需要承担为处理间歇性函数调用而超额配置的低效虚拟机的成本。 有了 FaaS,随着资源调配的责任从用户转移到云提供商,过度调配资源的成本将由云提供商承担。 据报道,在 Azure Functions 中,"81% 的应用程序平均每分钟调用一次或更少。这表明,这些应用程序的保温成本与其总执行(计费)时间相关,可能高得令人望而却步"。 尽管 FaaS 是一个热门研究课题,但研究界对这一问题缺乏定量了解,主要原因是缺乏有关大规模 FaaS 平台实际硬件利用率的报告,更不用说解决这一问题的方案了。

Meta运营着一个超大规模的私有云,其中包含一个名为 XFaaS 的 FaaS 平台。 XFaaS 每天在分布于数十个数据中心区域的 10 多万台服务器上处理数万亿次函数调用。 由于 XFaaS 的超大规模,降低硬件成本是我们的首要任务。 因此,论文首先描述了额外硬件成本的风险,然后介绍了Meta如何在 XFaaS 中解决这些问题。

关于额外的硬件成本,具体包括

  • 冷启动时间长。一个function的生命周期,中大部分步骤的成本都由云提供商承担。此外,还有一些因为warm up等待造成的开销。
  • 负载变化大。负载变化大可能会导致硬件成本过高,或者当负载激增时系统过载。如果为处理峰值负载而调配资源,XFaaS 将面临严重浪费的风险。
  • 下游服务超载。即使 FaaS 平台能够完美地管理自身负载,其功能仍会对所调用的下游服务造成资源争用。过去,我们曾经历过由于非面向用户的函数调用激增导致数据库超载,从而造成面向用户的在线服务出错率过高而造成的中断。AWS Lambda 等现有系统为每个函数设置了静态的流量限制,但这是不够的。如果限制设置过低,FaaS 平台和数据库都会浪费未使用的资源(根本打不满)。相反,限制设置过高可能会导致在线服务出错率过高。

解决方案主要包括

  • Cold start time
    • proactively pushes the latest code of all functions to all servers
    • runs multiple functions concurrently in one Linux process
    • cooperative JIT compilation
    • function – locality function groups – locality worker groups
  • High variance of load
    • time-shift computing
    • dispatches function calls across datacenter regions
    • function quotas
    • future execution start time
  • Downstream overloading
    • resource isolation and management system
    • back-pressure signals

Background

Rapid Growth of FaaS in Our Private Cloud

随着 FaaS 在公共云中的迅速普及,人们不禁要问,私有云是否有足够的 FaaS 工作负载来支持类似的增长。 图 3 显示,XFaaS 中的每日函数调用次数在过去 5 年中增长了 50 倍,目前每天的函数调用总数达到万亿次。 2022 年底的快速增长是由于推出了一项新功能,允许使用类似于 Kafka 的数据流来触发函数调用。 总之,XFaaS 的快速应用表明,FaaS 是一种成功的编程范例,同样适用于公共云和私有云环境。

Spiky Load

XFaaS 客户端以高度尖峰的方式提交函数调用。 峰值需求是非峰值需求的 4.3 倍。 午夜高峰是由类似 Hive 的大数据管道触发的函数调用造成的,这些管道会在午夜前后创建数据表。 处理函数的触发一般就是绑定在数据的可用性上的。

图 4 举例说明了一个函数在 15 分钟时间窗口内提交近 2000 万次函数调用的尖峰负载。 如果不加区分地分配容量以满足峰值需求,就会在非峰值时间造成大量硬件浪费。

我们的一个重要见解是,某些 XFaaS 函数可能不需要立即执行。 大多数 XFaaS 函数都是由队列、定时器、存储、协调和事件桥工作流触发的。 与通过 HTTP 等直接 RPC 触发的函数相比,这些函数的延迟容忍度更高。 延迟预期宽松的函数提供了平滑负载的机会。 XFaaS 采用一整套技术来处理尖峰负载。必要时,它会推迟执行延迟容忍函数和低关键性函数。 此外,它还会跨数据中心区域全局调度函数调用,以平衡负载。 此外,它还强制执行每个函数的配额,以确保公平性。 最后,它允许调用者指定函数的未来执行时间,从而以可预测的方式分散负载。

Datacenters and Uneven Hardware Distribution

Meta的私有云由数十个数据中心区域和数百万台机器组成。 每个区域由多个数据中心组成,这些数据中心在物理上相互靠近。 由于连接同一区域内数据中心的网络具有低延迟和高带宽的特点,我们可以在很大程度上认为区域内的硬件是可互换的。

但是,跨区域网络带宽比区域内数据中心之间的带宽低约 10 倍,跨区域网络延迟比区域内延迟长约 100-1000 倍。 因此,Meta的服务通常会区别对待区域内和跨区域通信。因此,XFaaS 需要在区域本地性和跨区域负载平衡之间取得平衡。

图 5 显示了 XFaaS worker池在部分数据中心区域的容量。 由于容量的增量获取和容量的可用性,XFaaS 的容量在各区域分布不均。 这就要求 XFaaS 针对区域位置进行优化,同时平衡各区域的负载,以提高硬件利用率。

Diverse Workloads

Workload Categories

根据triggers可以将函数分为三类: (1) 队列触发函数,通过队列服务提交; (2) 事件触发函数,由数据仓库和数据流系统中的数据变化事件激活; (3) 定时器触发函数,根据预先设定的时间自动触发。

在一个月内,XFaaS 执行了 18,377 次独特的函数。 表 1 总结了这些函数的函数数、调用次数和计算使用情况。 就函数数而言,队列触发函数因其最长的使用历史而占据主导地位。 然而,一旦 XFaaS 开始支持事件触发式函数,许多数据处理服务就开始使用这些函数,从而导致其迅速增长。 这些函数往往运行频繁,但执行时间较短。 因此,事件触发函数占调用次数的 85%,但只占计算使用量的 14%。 对定时器触发函数的支持是 XFaaS 最新添加的功能,其发展势头依然强劲。

Workload Examples

一般来说,XFaaS 工作负载很少处理需要亚秒级响应时间的面向用户的交互式请求,例如新闻推送显示、搜索结果排名或视频播放。 传统上,在 Meta,这些交互式用户请求由长期运行的服务处理,因为无服务器函数在这些场景中并不具有显著优势。

无服务器功能通常有两个主要优点: (1)即用即付,无需预先规划容量;(2)简化部署,开发人员只需编写代码,无服务器平台会自动处理部署。 但是,对于需要亚秒级响应时间的面向用户的请求来说,第一个优点就失去了意义,因为需要进行细致的容量规划,以提供有保障的容量,确保 Meta 产品的数十亿用户获得愉悦的体验。 这与用户群有限的小型产品的情况截然不同,在这种情况下,偶尔的用户体验下降是可以接受的,而且云提供商预计会有备用容量来应对小型产品的负载高峰。

此外,在 Meta,无需使用无服务器函数即可通过完全自动化部署实现第二个优势。 值得注意的是,Meta的持续部署工具 Conveyor,已经在没有任何人工干预的情况下部署了 97% 的服务,甚至无服务器函数也是通过 Conveyor 部署的。 由于这些原因,在 Meta,无服务器函数很少用于处理需要亚秒级响应时间的面向用户的请求。

接下来是Meta中使用的几个典型的 XFaaS 工作负载示例:

  • 推荐系统
  • Falco,一个记录所有类型事件的logging 平台
  • Productivity Bot 是一个基于规则自动执行各种任务的平台。例如,可以创建一个规则,在代码变更部署到生产中时发送消息。
  • Morphing Framework 是一个以编程方式生成短暂函数的平台,用于执行跨数据存储(如 MySQL、键值存储)的自定义数据转换。这些函数可运行数分钟,消耗的 CPU 周期比普通函数多出数个数量级。

Workload Resource Usage

主要讲functions的资源使用率变化很大。

XFaaS Design

Overview

为启动函数调用,客户端向submitter发送请求。 submitter执行速率限制,并将请求转发给队列负载平衡器(QueueLB),然后队列负载平衡器会选择一个持久队列(DurableQ)来持续处理函数调用,直到调用完成。 调度程序会定期从 DurableQ 中提取函数调用,并将其存储到内存中的 FuncBuffer(函数缓冲区)中,每个函数都有一个单独的缓冲区。 调度器根据函数调用的关键性、完成截止日期和配额来决定函数调用的执行顺序,并将准备好执行的函数调用转移到 RunQ(运行队列)中。 最后,RunQ 中的函数调用会被分派到 WorkerLB(worker负载平衡器),后者会将它们路由到适当的worker处执行。

在图 6 中,DurableQ 是唯一一个sharded的、有状态的组件,而其他组件都是无状态和unsharded的。 DurableQ 使用高度可用的分片数据库来永久存储函数调用。 子监控器、队列库、调度器和 WorkerLB 都是无状态、无分片和复制的,没有指定的领导者,因此它们的副本扮演着同等的角色。 这种架构将状态管理集中在一个组件中,简化了整体设计。无状态组件的扩展可以通过添加更多副本轻松实现,如果无状态组件出现故障,任何对等节点都可以接管其工作。

在 XFaaS 的超大规模中,图 6 中的每个组件通常都运行在数百台或更多台服务器上,但 Worker 和 DurableQs 除外。 这些组件充当函数调用的计算和存储引擎,它们的容量需求与函数调用量成正比。 目前,Worker 和 DurableQs 分别消耗了超过 10 万台和 1 万台服务器。

XFaaS 在多个地理分布的数据中心区域运行,能够向任何区域调度函数调用。 不过,它的目标是在负载平衡和区域本地性之间取得平衡,在提交函数调用的同一区域执行大部分函数调用。 如图 5 所示,XFaaS 的硬件容量在各区域之间存在显著差异,因此负载平衡至关重要。 为实现这一目标,三个组件协同工作。 首先,QueueLBs 在不同区域的 DurableQs 之间平衡负载。 其次,调度器根据每个区域的worker池容量按比例分配函数调用。 最后,WorkerLB 在平衡单个 Worker 的负载的同时,还确保每个 Worker 处理一个稳定的函数子集,以提高locality,而不是处理所有函数。

为确保容错,图 6 顶部的中央控制器与函数执行的关键路径保持分离。 控制器通过定期更新关键配置参数来不断优化系统,这些参数由工作器和调度器等关键路径组件消耗。 由于关键路径组件缓存了这些配置,因此即使中央控制器发生故障,它们也能继续使用当前配置执行函数。 但是,当中央控制器宕机时,XFaaS 不会根据工作负载变化重新配置。 例如,即使实际流量发生了变化,用于跨区域函数调度的流量矩阵也不会更新。 通常情况下,这不会立即导致严重的不平衡,而且可以承受中央控制器停机数十分钟。

为简单起见,图 6 仅显示了一个命名空间中每个区域的一个工作池。 实际上,一个区域可以承载多个命名空间,图 6 中描述的每个组件都可以同时支持多个命名空间,只是每个命名空间都有自己的专用工作池。 下面,我们将详细介绍每个 XFaaS 组件。

Submitter

函数可以根据各种事件执行。

第一类事件涉及客户端向提交者提交函数调用,如图 6 所示。 第二类事件涉及数据仓库中的数据变化,而第三类事件则由数据流系统中的新数据到达时触发。为简单起见, 图 6 中未显示后两类事件。

最初,XFaaS 允许客户直接向 DurableQs 写入要调用的函数的 ID 和参数。 随着函数调用率的提高,我们引入了提交器,通过批量调用并将其作为一个操作写入 DurableQ 来提高效率。 如果函数的参数过大,提交器会将参数分别存储在分布式键值存储中。 此外,提交器还会执行速率限制等策略,以避免提交器和下游组件负载过重。 为此,每个提交器都会独立查询图 6 所示的中央速率限制器,以跟踪全局资源使用情况。

最后,如图 2 和图 4 所示,由于某些客户的函数调用提交率非常sharp,因此每个区域都有两组提交器,一组用于正常客户,另一组用于非常sharp的客户(如图 4 中的客户),以防止sharp客户对正常客户造成过大影响。 XFaaS 会监控尖峰客户,并提醒操作员与客户协商,将其转移到尖峰提交器;否则,XFaaS 将默认对其进行节流。请注意,这需要人工参与,因为这对客户来说是明确的 SLO 更改。

DurableQ and QueueLB

(这边其实没有完全搞懂,感觉有些模模糊糊的)

在收到函数调用后,提交者会将其转发到 QueueLB。 图 6 顶部所示的配置管理系统称为 Configerator。它存储路由策略并将其传递给每个队列栈,该策略规定了每个 "源区域-目的地区域 "对的提交者到队列栈流量的分配。 这有助于平衡不同地区 DurableQ 的负载,因为不同地区 DurableQ 的硬盘容量也有很大差异,如图 5 所示。 函数调用到 DurableQs 的映射是通过随机 UUID 分割的,以便在 DurableQs 之间平均分配负载。这意味着函数调用可以在任何 DurableQ 上排队。

每个 DurableQ 都为每个函数维护一个单独的队列,队列按函数调用的执行开始时间排序,执行开始时间由调用者指定,可以是未来的某个时间,比如从现在开始的 8 个小时。 调度程序会定期查询 DurableQ,以检索开始时间已过当前时间的函数调用。 一旦一个 DurableQ 向一个调度程序提供了函数调用,它就不会再向另一个调度程序提供,除非该调度程序执行失败。 在 Worker 执行函数调用后,调度程序会向 DurableQ 发送 ACK 消息,表示函数已成功执行;或发送 NACK 消息,表示函数未成功执行。 收到 ACK 后,DurableQ 会从队列中永久删除函数调用。 如果收到 NACK 或超时后既未收到 ACK 也未收到 NACK,它就会将函数调用提供给其他调度程序检索和重试。 这意味着 DurableQ 提供了 "至少一次"(at-least-once)语义。

Scheduler

调度程序的主要职责是根据函数调用的关键性、执行期限和容量配额确定函数调用的顺序。 如图 6 所示,调度程序的输入是多个 FuncBuffers(函数缓冲区),每个函数一个,输出是一个有序的 RunQ(运行队列),其中包含将被分派执行的函数调用。FuncBuffers 和 RunQ 都是内存数据结构。

每个调度程序都会定期轮询不同的 DurableQ,以检索待执行的函数调用。 由于函数调用到 DurableQs 的映射是通过随机 UUID 分割的,调度程序可能会从不同的 DurableQs 获取不同调用者对同一函数的调用。 这些调用被合并到函数的单个 FuncBuffer 中,该 FuncBuffer 首先按函数调用的重要程度排序,然后按其执行期限排序。 由于 XFaaS 经常在容量不受限制的情况下运行,因此先按关键性排序可确保重要的函数调用更有可能在容量紧张或站点中断时被执行。

调度程序会从所有 FuncBuffer 的前几项中选择最合适的函数调用,并将其移动到 RunQ 中执行。 选择标准涉及配额管理,将在第 4.6 节中详细介绍。

RunQ 还具有流量控制功能。 如果函数执行缓慢导致 RunQ 增大,调度程序就会减慢将项目从 FuncBuffers 移至 RunQ 的速度,以及从 DurableQs 中检索函数调用的速度。

当某个区域的工作者利用率不足时,为了平衡负载,该区域的调度程序可能会从其他区域的 DurableQs 中检索函数调用,因为其他区域的工作者负载过重。 图 6 中的 "全球流量指挥器"(Global Traffic Conductor,GTC)对所有区域的需求(待处理的函数调用)和供应(工人池的容量)保持近乎实时的全局视图。 它定期计算流量矩阵,其中的元素 𝑇𝑖 𝑗 指定了区域 𝑖 的调度程序应从区域 𝑗 提取的函数调用的比例。 为了计算流量矩阵,GTC 首先设置∀𝑖,𝑇𝑖𝑖 = 1,∀𝑖≠ 𝑗,𝑇𝑖𝑗 = 0,这意味着所有调度器只从其本地区域的 DurableQ 调用。 然而,这可能会导致某些区域的工人超负荷工作。GTC 会计算将这些超载区域的流量转移到附近区域,直到没有区域超载或所有区域的流量相同。 GTC 通过图 6 中的配置管理系统定期向所有区域的所有调度员分发新的流量矩阵。 然后,调度员根据流量矩阵从不同区域的 DurableQs 获取函数调用。

Workers and WorkerLB

每个命名空间支持一个(不清楚是不是有且仅有一个)运行时,并有自己专用的工作池。 使用相同编程语言但需要较强隔离性的函数会被分隔到不同的命名空间。 为简洁起见,下面的讨论假设只有一个命名空间,即只有一个运行时和一个工作池。

为了最大限度地提高硬件效率,我们希望接近通用 Worker 的效果,即每个 Worker 都能立即执行每个函数,而无需任何启动开销。 XFaaS 通过多种技术实现了这一目标。

  • 首先,它允许多个函数在一个运行时实例(即一个 Linux 进程)中并发执行。
  • 其次,它使用高效的点对点系统,主动将命名空间中所有函数的最新代码推送到该命名空间中每个工作者的固态硬盘上。Worker 可保持runtime始终正常运行。在首次收到函数调用时,运行时会从本地固态硬盘加载预填充的函数代码并立即执行。运行时的一个实例可以在不同的线程中并发执行不同的函数。
  • 第三,XFaaS 在工作者之间使用合作 JIT 编译,以消除每个工作者为 JIT 编译而重新进行剖析的开销和延迟。
  • 最后,为了提高 Worker 内存中函数代码和 JIT 代码的缓存命中率,XFaaS 使用了locality群组,以确保only calls for a stable and small subset of functions are dispatched to a given worker

Cooperative JIT Compilation

我们在讨论中主要以 PHP 为例。 我们的 PHP 运行时称为 HHVM [23],它使用基于插桩的profiling来实现基于区域的 JIT 编译 [36]。 然而,让数以万计的工作者各自独立执行剖析是低效的,因为之前的工作[37]表明,HHVM 需要长达 25 分钟才能完成剖析并生成高质量的 JIT 代码(代码大小约为 500MB)。 这对 XFaaS 来说是个问题,因为它经常向所有工作站推送新的函数代码。

为了解决这个问题,XFaaS 采用了合作式 JIT 编译。 每隔三小时,XFaaS 会将所有新的和已更改的函数代码捆绑到一个文件中,并通过点对点数据分发将其推送给所有工作者[15]。 工人分三个阶段开始使用新的函数代码。 在第一阶段,一小部分工人运行新代码,以捕捉潜在的错误。 在第二阶段,2% 的工人运行新代码,以捕捉更难发现的错误,一些播种者工人执行剖析,以收集 JIT 编译所需的数据。 在第三阶段,播种者的剖析数据会分发给与播种者处于同一定位组的所有工人,这样他们就能在收到新代码的函数调用之前,立即对热门函数执行 JIT 编译。 之后,当它们收到这些调用时,就能立即执行优化后的 JIT 代码,而不会有任何启动或剖析延迟。

Locality Groups

我们的目标是接近通用 Worker 的效果,即每个 Worker 都能持续执行每个函数,而无需任何启动开销。 然而,由于内存容量有限,在每个 Worker 的内存中保留每个函数的 JIT 代码是不可行的。 此外,函数本身也需要内存来缓存数据和执行计算。 如果一个 Worker 同时执行多个对内存要求较高的函数,它的内存可能会耗尽。 例如,第 3.2 节中描述的 Morphing Framework 的函数运行时间很长,在完成之前会消耗越来越多的内存。

为了解决这个问题,图 6 所示的 "本地优化器 "将函数和 Worker 分成本地组,以确保只有函数子集的调用才会被分派到给定的 Worker。 根据函数的剖析数据,定位优化器将函数划分为不重叠的定位组,确保占用内存的函数被分散到不同的定位组中。 具体到 Morphing Framework,由于它以编程方式生成了许多短暂函数,而且这些函数具有相似的特性,因此定位优化器只需以循环方式将它们分配到不同的定位组。

除了将函数映射到局部性群组外,每个函数局部性群组还映射到相应的 Worker 局部性群组, 如图 6 中的局部性群组 1 和 2。 图 6 中的 WorkerLB 在路由函数调用时,会从函数对应的工人本地化组中随机选择两个工人,并将函数调用分派给负载较轻的工人。 这种方法将局部性引入了传统的随机二选一方法。 当函数的资源消耗情况发生变化时,局部性优化器可以在局部性组之间动态地重新分配函数。此外,如果函数调用的组合发生变化,例如某个局部组的函数调用激增,局部性优化器可以将工人从一个局部组转移到另一个局部组,以平衡负载。

Handling Load Spikes

Quota for Functions

每个函数都与其所有者设定的配额相关联,该配额定义了该函数每秒在全局范围内可使用的 CPU 周期总数。 将配额除以函数每次调用的平均成本,即可将配额转换为每秒请求数(RPS)速率限制。 一个函数的 RPS 会在全局范围内汇总,每个调度器都会参考图 6 中的中央速率限制器,根据函数是否在全局范围内超过其 RPS 限制,来决定是否对函数的调用进行节流。

Time-Shifting Computing

XFaaS 提供两种配额:保留配额和机会配额。

如果函数使用了预留配额,且当前配额在其分配的限额内,XFaaS 会努力在收到调用后几秒内在 Worker 上启动函数调用的执行。 这是 XFaaS SLO 的一部分。

如果函数使用机会配额,XFaaS 对该函数的执行 SLO 设置为 24 小时。 这样,XFaaS 就能将这些容错函数的执行时间安排在有可用容量的非高峰时段。

当工人利用率不足或超负荷时,XFaaS 会动态调整使用机会配额的函数的调用率,使其运行速度高于或低于根据其配额得出的 RPS 限制。 让 𝑟0 表示机会主义函数的预设 RPS 限制。它的实际 RPS 限制会动态调整为 𝑟 = 𝑟0 × 𝑆,其中 𝑆 反映了当前工人的利用率。(这个S是利用率越小,S越大) 如果工人利用率不足,𝑆 就会增加。反之,如果工人超负荷工作,𝑆 就会一直下降到零,导致机会函数调度停止。 图 6 中的 "利用率控制器 "会监控工人的利用率,动态调整𝑆 以达到目标工人利用率水平,并将𝑆 保存在数据库中。 调度器定期从数据库中检索𝑆,并利用它计算机会主义函数的调整后 RPS 限制。Meta 的硬件预算分配流程激励团队尽可能利用机会配额,类似于公共云对使用收获虚拟机的定价激励。

Protecting Downstream Services

即使 XFaaS 能够完美地管理自身负载,其函数仍会对所调用的下游服务造成资源争用。

接下来的生产中断最初促使我们开发解决方案来解决这个问题。 名为 TAO 的社交图数据库一度在高峰时段出现高负载,这在意料之中。 然而,在 XFaaS 上运行的一个大容量函数带来的额外负载意外地加剧了这种情况,使 TAO 超载,并导致过多的故障和重试。 这些故障和重试扩大了对几个下游服务的查询,导致 TAO 可用性下降,并进一步导致索引服务超载,随后引发了多米诺骨牌效应。为确定多米诺骨牌效应的根本原因而进行的调查以及随后的手动缓解措施耗时一整天,最终导致 XFaaS 上的大部分函数执行被手动暂停,以暂时缓解这种情况。

为避免下游服务超载,XFaaS 采用类似于 TCP 的 "加-增-乘-减"(AIMD)方法来动态调整函数的 RPS 限制。 下游服务可抛出背压异常,以表明其已超负荷。 当某个函数的背压超过阈值时,其 RPS 限制 𝑟 会被调整为当前 RPS 限制的一部分 𝑀,即 𝑟𝑘+1 = 𝑟𝑘 × 𝑀。 当不存在背压时,RPS 限值会以加法形式增加,即 𝑟𝑘+1 = 𝑟𝑘 +𝐼。 𝑀和𝐼都是可调参数。背压阈值按下游服务定义,由服务所有者根据其领域知识设定。 例如,对于我们最大的两个下游服务,阈值设定为每分钟 5,000 个异常,在过去两年中,该阈值一直保持稳定,没有发生过任何变化。

XFaaS 还提供其他流量整形功能来帮助下游服务。 让 𝑟 表示函数的 RPS 限制,𝑝 表示函数的执行时间。 在任何给定时间内,函数最多可以有 𝑅 = 𝑟 × 𝑝 运行实例。 如果𝑝很大,𝑅 也会很大,这可能会导致下游服务的并发调用过多,从而可能造成服务过载。 XFaaS 允许为每个函数配置并发限制,以指示函数在任何给定时间内可运行的最大实例数。 并发限制的功能不如上述 AIMD 方法强大,但可作为一个适当的安全网,因为并非每个下游服务都能很好地在过载情况下抛出反压异常。

虽然某些下游服务可以在稳定状态下处理稳定状态下的高 RPS,但突然的变化可能无法很好地处理。 这些服务可能需要时间来预热缓存,或使用自动扩展来增加实例以应对负载的增加。 为了帮助这些服务,XFaaS 使用慢启动来逐步提高函数的 RPS,同时对 RPS 的变化率施加限制。 具体来说,如果每个时间窗口的函数调用次数 𝑇 已超过阈值,则每个时间窗口的流量最多会增加 𝛼。 通过经验评估,我们将这些参数设置为 ︰1 分钟,𝑇 = 100 次函数调用,𝛼 = 20%。慢速启动会缓慢但稳定地提高 RPS,直到达到 RPS 限制或并发限制,或者函数的完成率跟不上计划函数调用的速度。

Data Isolation

为了安全或性能而需要强隔离的函数被分配到不同的命名空间,每个命名空间使用不同的工作池来实现物理隔离。 在同一个命名空间中,多个函数可以在同一个 Linux 进程中运行,这得益于私有云内部的高度信任、对所有代码更改的强制同行审查以及已经到位的默认安全机制。 此外,我们还在各函数间强制执行数据隔离,以防止意外的数据滥用。 为此,我们采用了一种多层次安全信息流方法,其中包含贝尔-拉-帕杜拉(Bell-La-Padula)式访问控制[7]。

XFaaS 提供了一种编程模型,在这种模型中,函数所有者可以用语义注释其函数的参数。 系统会自动推断数据类型语义,并对缺失或可能不准确的注释发出警告。 为确保跨函数的数据隔离,XFaaS 执行 Bell-La-Padula 安全原则。 较高分类级别的负责人不得向较低分类级别写入数据,较低分类级别的负责人不得从较高分类级别读取数据。 换句话说,数据只能从较低的分类级别流向较高的分类级别。

XFaaS 在系统之间的边界执行策略,这些系统被分成不同分类级别的隔离区。 每个函数调用都标有一个隔离区,可以在函数编码时静态标注,也可以在 RPC 调用时动态标注。 XFaaS 调度器会检查来自源隔离区的函数参数是否能流向函数的执行隔离区,同时尊重 Bell-La Padula 属性。 同样,工作者也会确保在区域中运行的函数遵循这些属性。 总之,XFaaS 对隔离模型组合的新颖使用使其能够安全高效地运行。 一方面,它使用独立的 Worker 池来执行需要强隔离的函数。 另一方面,同一命名空间内的函数依靠语言运行时在函数粒度上保持数据隔离。 这样,XFaaS 就能在单个 Linux 进程中同时运行多个函数,从而最大限度地提高效率。

XFaaS and Public Cloud

Insights:

  • Insight 1: 优先保证local region的execution,但是全局都能调度
  • Insight 2: 优化resource utilization和throughput of function calls很重要
  • Insight 3: 很多任务都是能够delay-tolerant的

Advices:

  • Advice 1: Specify future execution start time有助于spread out execution
  • Advice 2: 以DDL方式选择SLO有助于postpone delay-tolerant函数的执行
  • Advice 3: Criticality level的指定有助于保证关键函数的执行