3.7.3 函数计算系统设计
在设计函数计算系统时,根据内外部用户的场景和需求要实现以下目标。
可靠性。系统在面对进程、机器、网络、可用区等不同程度的故障时,要有 自愈能力。在应用负载快速变化时,系统应当能及时准备好所需计算资源, 保障稳定的服务质量。
多租户资源共享和隔离。由于用户按实际使用的资源付费,所以计算资源要被不同用户的不同应用共享、降低系统成本。系统要具备出色的隔离能力, 避免应用相互干扰。
安全性。函数计算的定位是通用计算服务,要能执行任意用户代码,因此安全是不可逾越的底线。系统应当从权限管理、网络安全、数据安全、运行时安全等各个维度全面保障应用的安全性。
函数计算采用微服务架构,如图 3-89 所示,主要由以下几部分组成。
图 3-89 函数计算系统架构
账户管理服务。负责新账户的集群分配,以及从集群水位管理、用户隔离角度出发,在不同集群间迁移账户。
前端服务。提供函数计算对外的 API,验证请求的身份,将请求发送给对应的函数实例,收到结果后将其返回给用户。
函数实例调度服务。函数实例是执行函数请求的容器。实例调度服务追踪函数 / 用户的负载变化,实时伸缩函数实例,并在实例数超过配额时进行流控。
计算资源。对底层计算资源进行抽象,能够对接多种资源池,包括容器、虚拟机或者物理机。
计算资源弹性调度
在函数计算中,应用所需的计算资源由平台负责管理。平台要能够识别应用特征,在负载快速上升时,及时扩容计算资源,保证应用性能稳定 ;在负载下降时, 及时缩容计算资源,加快资源在不同账户 / 函数间的流转,提高资源利用率。因此更
实时、更主动、更智能的弹性伸缩是函数计算系统具备良好用户体验的关键。函数计算的资源伸缩过程是一个指标收集、在线决策、离线分析、决策优化的闭环。
系统收集多个维度的指标,既有节点维度的信息,包括 CPU、内存、网络资源 使用率、系统负载等;也有应用维度的信息,包括请求速率、函数实例启动延时、函数执行延时等。这些指标按照用户、函数、不同大小的时间窗口等被聚合,最终作为决策依据,被在线伸缩和流控算法使用。
随着平台规模的扩大,记录和聚合海量函数的信息在工程实现上面临较大的挑战。实践中,信息的处理代价和方式应当被仔细考量,以避免影响系统性能。比如, 统计 95 分位请求延时的成本较高,这类信息一般只在离线算法中使用,并通过统计算法计算近似的 95 分位信息,降低计算复杂度。
用户函数的调用通常是不均匀的,例如 1 秒内发生了 1000 次调用的函数,可能所有的调用请求都是在前 100 毫秒内发生的。如果所有请求都立即被响应,无疑需要在短时间内扩容更多的函数实例。待突发请求过后,很多函数实例又处于闲置状态。这样不但增加了系统压力,也降低了资源使用率。函数计算系统通过队列平滑负载毛刺的压力。函数调用请求首先在队列中排队,只有在有可用的函数实例后才被处理。系统按照如下的步骤计算所需的函数实例数。
(1)根据函数负载特点确定聚合请求数据的时间窗口,计算请求入队速率。对于 被密集调用的函数,数据聚合时间窗口不能太大,否则无法识别快速变化的负载,影响性能。对于被稀疏调用的函数,太小的聚合时间窗口可能聚合不到请求,没有意义。
(2)根据入队速率、队列长度、函数实例启动时长、实例生命周期、实例资源利用率等指标,计算期望的请求出队速率。
(3)根据出队速率及函数的执行时间,计算期望的函数实例数。
(4)对比已有实例数、正在启动的实例数和期望实例数,创建或销毁实例。
在创建新实例时,系统需要决定如何将函数实例放置在下层计算节点上。放置算法应当满足多方面的要求,如图 3-90 所示。
图 3-90 函数实例放置算法示例
容错。当函数有多个实例时,将其分布在不同的计算节点 / 可用区上,提高函数的可用性。
资源利用率。在不损失函数性能的前提下,将计算密集型、I/O 密集型等函 数调度到相同的计算节点上,尽可能充分利用节点的计算、存储和网络资源, 动态迁移不同节点上的碎片化实例,进行“碎片整理”,提高资源利用率。
性能。例如复用曾经启动过相同函数实例的节点,利用缓存的代码数据,加速函数的启动时间。
除了在线调度,系统还将天、周或者更大时间范围的数据用于离线分析。离线分析的目的是利用全量数据验证在线调度算法的效果、调优参数。通过数据驱动的方式,函数计算的调度大大加快了资源的流转速度,提高了集群整体资源的利用率。
负载均衡和流控
函数调用请求到达前端服务器(Frontend Server)后,首先向资源调度服务申请函数实例,再将请求发送给相关的函数实例处理。因此资源调度服务是系统的关键链路。为了支撑每秒近百万次的资源调度请求,我们将资源调度服务的负载进行分片(Partition),横向扩展到多台机器上,避免单点瓶颈。分片机制包含两层信息的映射:首先将指定函数的资源调度请求映射到对应的分片上;然后将分片调度请求映射到对应的分片服务器(Partition Server)上。函数实例服务负载均衡架构如图 3-91 所示,前端服务器首先确定请求所属的分片,然后通过分片映射表找到对应的分片服务器(函数实例调度节点),再将请求发送给该节点处理。
图 3-91 函数实例服务负载均衡架构
分片管理器(Partition Manager)将负载拆分为 N 个分片,并为每个分片指定对应的分片服务器,将分片的位置信息存储在分片映射表中(Partition Map Table)。分片服务器通过周期性的心跳向分片管理器汇报分片和节点的负载信息,分片管理器通过以下三种分片操作实现负载均衡。
分片迁移(Partition Move),将分片迁移到另一台分片服务器。当分片管理器发现某一台服务器的负载较高时,可通过分片迁移来平衡负载。
分片分裂(Partition Split),将负载很高的分片分裂为多个分片。通常一个分片上主要的请求来自少数被密集调用的函数。当函数负载增加超过一台分片服务器的处理能力时,分片管理器将相关分片分裂为多个分片,并指派给不同的分片服务器处理。
分片合并(Partition Merge),将多个低负载的分片合并为一个分片。
分片管理器监控整个集群的分片和服务器负载,通过分片的迁移、分裂、合并三种操作实现集群处理能力的横向扩展和负载均衡。集群不但可以支撑近乎无限的函数,而且单个函数的处理能力没有上限。
在多租户环境下,流控是保证服务质量的关键。对用户而言,当函数的调用量超过预期值时,例如函数逻辑错误产生大量非预期调用,系统应当及时流控,确保用户的费用可控。函数计算允许用户配置最大函数运行实例数来限制应用负载。当应用负载上升、系统在扩容时,如果函数实例数达到配额上限,那么将停止扩容并返回流控错误。除了用户层面的流控,当函数的负载动态变化导致节点过载时,系统也应当及时流控,避免用户间互相影响。例如在一个分片上,多个用户函数负载增加, 导致分片服务器过载。此时即使每个用户都未用满配额,系统也需要流控。在该场景下的流控,不但要及时,更要做到公平。调用量越大的函数,请求被流控的概率应当越高。
安全性
在函数计算中,应用的安全性由用户和平台共同负责。函数计算系统管理服务器 等IT 基础设施,负责平台的安全,包括硬件、操作系统及编辑语言运行时安全补丁升级、网络安全等。用户负责应用在平台上的安全,包括代码管理、敏感数据安全访问、身份认证和授权等。
每个函数都在独立、隔离的环境中被执行。函数计算提供与弹性计算相同的隔离强度。不同用户不会共享 VM,但同一个 VM 上能运行同一用户下的不同函数。函数计算会周期性地重置运行时环境,并自动更新操作系统及运行时依赖软件的安全补丁。函数计算隔离模型如图 3-92 所示。
图 3-92 函数计算隔离模型
在函数计算中,用户数据包括函数代码、函数配置、异步调用消息。函数代码和函数配置加密持久化存储在对象存储和表格存储中。异步调用消息临时存储在消息队列中。对象存储、表格存储和消息队列资源都由函数计算系统管理,用户不可见。函数计算系统通过 HTTPS 读写数据。
通过共享责任的安全模型,函数计算大幅度地降低了应用安全的复杂度。作为一个新兴领域,函数计算应用安全处于高速发展的时期。业界在系统可观察性、运行时 风险注入测试、应用安全分析等领域涌现出大量新兴的工具和最佳实践,帮助用户以更低的代价实现更好的安全性。