本文作者:史明伟 , 阿里云智能高级技术专家。
1 Serverless 异步任务处理系统诞生和挑战
无论是对于云的开发者,还是尝试业务升级的企业客户,Serverless的三个概念 “极致弹性、无服务器运维、 按需付费” 几乎已经深入人心;但关于 Serverless能做什么、怎么做,却仍然是围绕在大家身边最普遍的声音。
在Serverless研发的初始阶段,通常技术团队会更多聚焦于弹性,冷启动加速,希望通过弹性能力凸显产品技术竞争力,确立产品在市场上的领先地位,并依赖这些能力吸引开发者和企业客户使用Serverless,这个阶段,更多的是依靠技术影响力引导大家探索 Serverless。
随着我们对Serverless的不断深入理解,同时弹性能力的提升进入深水区,相对来说没有本质改变的情况下,我们将更多地思考触及Serverless弹性以外的其他价值,这个时候,弹性将作为系统的基础能力渗透在产品各个方面,需要更多的从系统性的角度考虑 Serverless能做什么,能给客户带来什么,如何让业务聚焦那些不得不需要定制化的部分?
系统性意味着我们需要从包括资源供给,弹性调度,应用框架,容量评估,运维观测等多个维度来考虑Serverless系统对于客户业务的价值, 哪些需要用户参与,或者少参与,哪些需要以服务化能力提供给客户;对于那些客户必须参与的,平台需要提供便利的开发工具满足开发阶段客户需求;在具体业务逻辑实现中,利用Serverless的灵活扩展性,尽可能快速的帮助开发者实现和其他云服务的快速连接,提供稳定高效访问是Serverless所能带给客户的核心价值。
面对实际的客户需求,产品需要考虑多种业务场景: 离线场景的耗时任务执行,在线场景的高并发请求处理,以及事件驱动场景的事件处理,如何将众多业务场景需求在一个计算平台上得到满足,是我们面临的最大挑战。
基于对 Serverless的不断深入理解,结合产品在弹性调度方面的不断积累,我们开始了基于多租架构的Serverless异步任务处理系统的构建,利用异步化的访问方式,帮助用户托管请求,以服务化的手段帮助用户快速执行任务,处理异常,提供可靠的执行保障,结合异步化的结果投递能力,希望能够面向事件驱动,在线业务处理,Serverless Job/Task 等复杂业务场景,为客户带来更多价值。
构建一个面向多租场景的Serverless异步任务处理系统需要面临各种挑战;这个时候我们需要化繁为简,分析任务系统到底要做什么:任务分发, 任务调度, 任务执行。
Serverless本质是一个多租分时复用的商业模式,关于Serverless异步任务处理系统的挑战,结合任务处理系统的功能需求,这些挑战可以总结归类为三个方面:
第一,多租架构本身带来的挑战:包括租户隔离问题,多租资源管理,以及如何权衡隔离性和成本之间的矛盾,最后还需要关注多租架构下自动诊断,快速定位问题的挑战;
第二,业务类型多样性和资源供给之间的矛盾带来的挑战:资源需求多样化,客户可能需要多种资源,比如面向 CPU密集型、IO密集型、内存密集型等;运行环境多样化,客户运行业务逻辑时,需要为任务提供与其对应的运行环境,需要面对多种运行环境;运行时长不确定性,面对离线和在线场景的不同业务特征,实际任务的执行时长差异巨大;最后,不同业务类型流量特征不同,流量不可预知等问题带来的请求处理,任务调度和流控策略方面的挑战;
第三,任务管理本身需要面临的挑战:包括任务生命周期管理,运行操作、 任务去重,任务执行状态追踪以及任务结果投递等相关问题带来的挑战。
以上挑战也是企业在构建分布式业务系统时所面临的最核心问题,站在产品的角度,我们希望能够通过异步任务系统的构建,帮助用户解决分布式系统中的这些共性的典型问题。客户只需要关注自己的业务请求提交和执行结果,从请求提交到执行的过程中涉及的Serverless弹性能力,资源调度,系统流控及可靠执行,错误重试等细节无需用户关心,真正实现 Serverless 倡导的几个核心理念:极致弹性,无服务器运维、按需付费,最终帮助兑现Serverless对于客户的业务价值。
在讨论了Serverless异步任务系统构建面临的多种挑战之后,通过上面的分析,Serverless异步任务处理系统功能可以简单概括为以下四个核心模块:
① 请求托管:负责多租架构的任务托管、用户请求存储、获取用户请求以及执行用户请求,如何在多组架构下选择合适的隔离粒度,更好的实现用户的请求隔离,如何在用户隔离需求的基础上更好的权衡隔离和成本之间的平衡。
② 流量控制:通常用户选择异步任务处理系统,大概率源于请求和消费之间存在矛盾。如何帮助客户解决用户请求以及后端资源供给之间的矛盾,更好地执行用户的任务请求,是异步系统核心所在,流量控制至关重要。
③ 执行管理:执行管理主要分为两个层面。首先,如何更好地执行任务,与流量控制更好的联动,如何用更好的方式调度资源从而更好地执行任务,这也是对系统底层调度的挑战。其次,如何管理任务请求,比如可能需要在任务执行过程中进行暂停、删除或批量操作,也需要对执行的任务提供状态追踪,了解其执行情况,并提供任务去重的能力。
④ 目标投递:也可以叫结果投递,需要将任务最终执行结果返回给用户。关于结果投递,这里有多个思考,一种是如何将结果投递;另一种是如何以更好的方式将结果投递;对于前者比较直观,而对于后者,我们希望对于任务执行结果,能够提供更灵活的再次处理能力,因此在实现上支持将结果投递到函数计算、MQ,EventBridge等更通用的产品上,用户可以基于这些产品再次利用事件驱动或相关能力对于任务执行结果进行后续的再次处理,包括发送短信、webhook 等,实现与异步任务系统进行上下游联动。
2 Serverless 异步任务处理系统多租户架构演进
下图展示了一个典型的异步任务处理系统的基本模型,通过 API 的方式进行任务提交、任务调度,任务执行,最后完成执行结果的投递。
传统的任务处理框架中,通常会基于服务网关进行任务调度、负载均衡和流控策略等能力的建设,这也是分布式系统构建中最基本、但又最核心,最复杂,最需要人力投入重点建设的部分;后端实现通常都是基于进程粒度的内存队列和运行时层面的线程池模型完成具体的任务派发和执行。
Serverless 异步任务处理系统流程为:用户通过 API 的方式进行任务分发,请求到达Serverless服务网关之后,被存储到异步请求队列中,Async Service将开始接管这些请求,然后请求调度获取后端资源,将这些请求分配给具体的后端资源进行执行。该架构图中 Async Service负责了传统架构中请求Dispatcher、负载均衡,流控策略和资源调度的实现,这时候函数集群相当于抽象的分布式线程池模型,在函数计算模型下,实例之间相互隔离,且资源具备水平伸缩的能力,可以避免传统应用架构下单机资源限制导致的线程池容量问题及资源调度瓶颈问题,同时任务的执行环境并不会受整体业务系统运行时的限制,这也是Serverless异步任务系统相比传统任务系统的价值所在。
从Serverless任务处理系统的架构来看,其处理逻辑非常简单,大部分分布式系统依赖的能力全部由Async Service系统角色透明化的进行了实现,对于用户而言更多的是通过函数化编程的方式提供任务处理的实现逻辑,整体架构避免了对基于语言运行时线程池的依赖,整个函数计算集群提供了一个“无限”容量的“线程池”,通过服务化的方式,用户只需提交请求,其他并发处理、流控以及积压处理全部由 Serverless平台负责完成。当然在实际的执行过程中,需要结合业务特征对异步任务处理的并发度,错误重试策略及结果投递进行一些配置。
接下来,我们将重点讲述Serverless异步任务处理系统构建中的一些技术细节。首先我们从任务分发及请求托管开始讲述Serverless异步任务系统构建过程。
Serverless多租架构下,异步调用请求首先到达系统的 API 网关,由API网关将接收到的异步请求放置到异步队列中托管;API 网关是一个无状态的架构设计,能够支持负载均衡和动态扩缩容。
简单的异步任务请求托管,在实际的系统实现中,不仅需要考虑租户之间的请求隔离,也需要考虑相同租户下不同函数之间的隔离需求;结合客观的请求隔离需要、请求处理的实时性要求,以及平衡队列资源的使用成本,我们设计了一套包含多种队列(Queue)类型,支持动态回收的队列管理系统,基于这样的系统实现满足了多租架构下隔离需求,执行效率,和隔离成本之间的平衡。
这些队列模型包括账号粒度的队列模型,函数粒度的队列模型以及多账号共享维度的队列模型;账号粒度的队列作为基本的执行保障,当队列中某个函数请求执行异常可能对其他函数请求产生影响的时候,会动态地为其分配函数粒度队列,并将该函数相关的请求路由到专属队列中进行处理;同时,对应的函数如果长期没有请求,在一定时间周期之后,会对之前分配的队列资源进行动态回收,达到队列资源的高效利用。
除了定义多种类型的队列模型之外,面对任务队列的切换,系统提供了任务请求的动态路由能力,可以自动化地将请求路由到不同类型的队列中,分发到不同的Partition上快速执行,解决由于Noise Neighbor问题引起的消费积压或请求负载不均带来的消费延迟问题。
有同学可能会疑惑,为什么不一开始给每个函数分配独立的请求队列?在云计算环境下,队列本身是一种资源,给每个账号下的每个函数分配一个Queue资源,看起来是彻底解决了请求的隔离问题,由于函数的量级和队列系统能够提供的队列量级是不对等的,考虑到下游系统的具体实现,结合底层为了满足实时性的消费处理逻辑,一个函数可能需要分配多个队列资源,通过并行消费多个队列满足请求处理的实施性要求,从实际来看,不仅要考虑Queue的分配性能,还需要考虑Queue资源分配对下游系统的冲击以及大量Queue资源本身的管理成本;每个函数持有一个Queue的成本也是巨大的,由于不同函数的负载不同,调用频率不同,为其分配独立的Queue在一定程度上本身就是一种资源浪费,与Queue密切相关的后端消费逻辑也会带来不必要的大量系统资源消耗,对于Serverless系统而言,这些都是非常巨大的系统资源浪费。
在讨论完请求托管的底层设计逻辑之后, 接下来我们将对于异步请求处理链路的流量控制策略做进一步的解释。
流量控制主要包括两个部分,一个是面向任务请求消费的动态负载能力,主要是Receiver能力的动态扩容和缩容;另一部分就是后端任务执行调度的反馈能力,通过及时将后端任务处理的结果返回给Receiver ,再通过调整 Receiver 获取任务请求的速率,最终适配系统后端资源,达到平衡。
具体实现上,系统采用AIMD的反馈控制算法,即和性增长,乘性降低。该算法在 Serverless的高频请求或多租架构下,能够实现更细粒度的控制。整个过程可以将 Receiver和Invoker看作一个Pool,通过结合Pool Size线性增长以及遇到后端消费能力不足负反馈时 Pool Size 乘性衰减,使得 Pool Size 动态收敛到一个匹配后端处理能力的大小,实现系统的流控管理,避免上游请求的不断获取对下游资源调度的冲击,也避免了极端情况下由后端资源调度问题引起的系统饥饿状态。
Job/Task模式下,业务对于Job和Task请求有追踪执行状态的需求,同时客户对于任务执行有一些高级管理,任务暂停,取消,任务去重等需求,我们在 Serverless异步任务的基本框架上增加了任务执行状态机的设计,通过引入状态机,可以对每个任务进行完整的状态追踪,可以基于状态追踪对任务执行进行细粒度的控制,也可以基于状态的控制实现异步处理的高级操作,比如任务删除、恢复、去重等高级任务管理功能,状态追踪也能够更好地体现任务生命周期的运行情况。
一个任务处理系统不仅需要帮助客户完成任务请求和执行,也需要关注任务执行过程中的调试或任务执行状态和各种执行指标的查询。
Serverless 系统为客户提供了非常完整的观测性能力,包括任务请求处理的情况、任务执行的耗时等,同时也提供了任务执行请求列表的查询,用户可以通过请求 ID 登录到正在执行的上下文,为用户提供了接近于传统操作习惯的过渡;此外,Serverless 系统实现了任务执行过程中生命周期各个阶段的耗时展示,为用户提供了从请求到执行完整的追踪能力。
3 Serverless 异步任务处理系统更大的价值
在Serverless异步任务处理系统中,用户的请求首先通过网关写入到异步队列中,异步队列本质是一个MQ, 异步任务处理系统本质是通过一系列的分布式消费策略,构建一个完整的Producer—Consumer模型,最终完成队列中每条请求的消费;将这样的系统模型进一步抽象,除了消费函数计算本身的异步请求,对于任何的MQ系统而言,都可以构造这样通用的消费处理层,也就意味着客户并不需要关注消费本身的系统实现逻辑,只需要提供对于消息本身的业务处理逻辑;EventBridge和函数计算的深度集成,正是通过这样系统化方式将消息消费与消息处理的逻辑进行分离,正如异步任务系统所做的那样,最终实现消息处理的Serverless化。
按照新的Serverless消息架构, 用户无需自己实现消费端的实现逻辑,不需要关注和担心与消费相关的负载均衡及流量管理等分布式系统问题,针对消息的业务逻辑以及对下游数据状态的更改,客户只需要利用函数计算快速实现自己的业务逻辑,达到快速构建业务系统的目的,这也是Serverless面向消息场景,能够给客户提供的新选择。
在具体实现上,底层基于EventStreaming模式,消费组件直接从消息源处拉取消息,无需中间BUS层转存直接投递到目标函数,实现消息的高效处理;整体架构实现了将有状态的消息消费逻辑和无状态的消息处理逻辑进行分离。
函数计算系统凭借其灵活的可扩展性和丰富的链接能力,能够快速帮助客户构建自己的业务系统;当然,这也对客户的业务系统构建提出了诸多的要求,例如需要按照函数编程范式进行代码开发,需要遵循函数计算运行模式,整体来看,客户需要基于Serverless架构对自己的业务系统进行一定的改造适配。
除了微观上提供的敏捷开发及灵活扩展能力之外,我们认为,函数计算系统真正的价值在于其整体的系统原子化能力,能够在宏观上作为客户系统在公有云环境的开箱即用扩展,使企业系统架构享受函数计算带来的资源弹性和灵活扩展能力。为了帮助企业利用 Serverless系统原子化的能力实现业务系统的延展,我们在 Serverless 的接入以及 Serverless 执行环境的支持上实现了更简单的接入方式。
请求提交层面,函数计算提供了HTTP接入方式,客户只需在自己的系统中集成函数提供的URL 即可将任务提交到公有云环境的函数计算引擎上。同时,函数计算构建了基于EventBridge的通用 PaaS 事件驱动能力,客户业务系统可以将相关事件投递到通用的基础设施上,然后通过EventBridge触发执行业务逻辑。
在业务系统的 Runtime 支持上, Serverless提供了多种接入方式,比如支持了传统的自定义镜像的运行。客户镜像无需进行任何改造,只需经过简单配置,即可将自己的任务和相关镜像托管在函数计算上,实现业务系统的快速接入,系统的灵活扩展和快速延展正是当下云原生架构所追求的目标。