乐趣区

关于阿里云:异步任务处理系统如何解决业务长耗时高并发难题

作者: 不瞋

当咱们构建一个利用,总是心愿它是响应迅速,老本低廉的。而在理论中,咱们的零碎却面临各种各样的挑战,例如不可预测的流量顶峰,依赖的上游服务变得迟缓,大量申请却耗费大量 CPU/ 内存资源。这些因素经常导致整个零碎被拖慢,甚至不能响应申请。为了让应用服务总是响应迅速,很多时候不得不预留更多的计算资源,但大部分时候,这些计算资源都是闲置的。一种更好的做法是将耗时迟缓,或者须要耗费大量资源的解决逻辑从申请解决主逻辑中剥离进去,交给更具资源弹性的零碎异步执行,岂但让申请可能被迅速解决返回给用户,也节俭了老本。

一般来说,长耗时,耗费大量资源,或者容易出错的逻辑,非常适合从申请主流程中剥离进去,异步执行。例如新用户注册,注册胜利后,零碎通常会发送一封欢送邮件。发送欢送邮件的动作就能够从注册流程中剥离进去。另一个例子是用户上传图片,图片上传后通常须要生成不同大小的缩略图。但图片解决的过程不用蕴含在图片上传解决流程中,用户上传图片胜利后就能够完结流程,生成缩略图等解决逻辑能够作为异步工作执行。这样应用服务器防止被图片解决等计算密集型工作压垮,用户也能更快的失去响应。常见的异步执行工作包含:

  • 发送电子邮件 / 即时消息
  • 查看垃圾邮件
  • 文档解决(转换格局,导出,……)
  • 音视频,图片解决(生成缩略图,加水印,鉴黄,转码,……)
  • 调用内部的三方服务
  • 重建搜寻索引
  • 导入 / 导出大量数据
  • 网页爬虫
  • 数据荡涤
  • ……

Slack,Pinterest,Facebook 等公司都宽泛的应用异步工作,实现更好的服务可用性,更低的老本。依据 Dropbox 统计,他们的业务场景中一共有超过 100 种不同类型的异步工作。一个性能齐备的异步工作解决零碎能带来显著的收益:

  • 更快的零碎响应工夫。将长耗时的,重资源耗费的逻辑从申请解决流程中剥离,在别的中央异步执行,能无效的升高申请响应延时,带来更好的用户体验。
  • 更好的解决大量突发性申请。在电商等很多场景下,经常有大量突发性申请对系统造成冲击。同样的,如果将重资源耗费逻辑从申请解决流程中剥离,在别的中央异步执行,那么雷同资源容量的零碎能响应更大峰值的申请流量。
  • 更低的老本。异步工作的执行时长通常在数百毫秒到数小时之间,依据不同的工作类型,正当的抉择工作执行工夫和更弹性的应用资源,就能实现更低的老本。
  • 更欠缺的重试策略和错误处理能力。工作保障被牢靠的执行(at-least-once),并且依照配置的重试策略进行重试,从而实现更好的容错能力。例如调用第三方的上游服务,如果能变成异步工作,设置正当的重试策略,即便上游服务偶然不稳固,也不影响工作的成功率。
  • 更快的实现工作解决。多个工作的执行是高度并行化的。通过伸缩异步工作解决零碎的资源,海量的工作可能在正当的老本内更快的实现。
  • 更好的工作优先级治理和流控。工作依据类型,通常依照不同的优先级解决。异步工作管理系统能帮忙用户更好的隔离不同优先级的工作,既让高优先级工作能更快的被解决,又让低优先级工作不至于被饿死。
  • 更多样化的工作触发形式。工作的触发形式是多种多样的,例如通过 API 间接提交工作,或是通过事件触发,或是定时执行等等。
  • 更好的可观测性。异步工作解决零碎通常会提供工作日志,指标,状态查问,链路追踪等能力,让异步工作更好的被观测、更容易诊断问题。
  • 更高的研发效率。用户专一于工作解决逻辑的实现,任务调度,资源扩缩容,高可用,流控,工作优先级等性能都由工作解决零碎实现,研发效率大幅提高。

工作解决零碎架构

工作解决零碎通常包含三局部:工作 API 和可观测,工作散发和工作执行。咱们首先介绍这三个子系统的性能,而后再探讨整个零碎面临的技术挑战和解决方案。

工作 API/Dashboard

该子系统提供一组工作相干的 API,包含工作创立、查问、删除等等。用户通过 GUI,命令行工具,后者间接调用 API 的形式应用零碎性能。以 Dashboard 等形式出现的可观测能力也十分重要。好的工作解决零碎该当包含以下可观测能力:

  • 日志:可能收集和展现工作日志,用户可能疾速查问指定工作的日志。
  • 指标:零碎须要提供排队工作数等要害指标,帮忙用户疾速判断工作的执行状况。
  • 链路追踪:工作从提交到执行过程中,各个环节的耗时。比方在队列中排队的工夫,理论执行的工夫等等。下图展现了 Netflix Cosmos 平台的 tracing 能力。

工作散发

工作散发负责工作的调度散发。一个能利用于生产环境的工作散发零碎通常要具备以下性能:

  • 工作的牢靠散发:工作一旦提交胜利后,无论遇到任何状况,零碎都该当保障该工作被调度执行。
  • 工作的定时 / 延时散发:很多类型的工作,心愿在指定的工夫执行,例如定时发送邮件 / 音讯,或者定时生成数据报表。另一种状况是工作能够延时较长一段时间执行也没问题,例如上班前提交的数据分析工作在第二天下班前实现即可,这类工作能够放到凌晨资源耗费低峰的时候执行,通过错峰执行降低成本。
  • 工作去重:咱们总是不心愿工作被反复执行。除了造成资源节约,工作反复执行可能造成更重大的结果。比方一个计量工作因为反复执行算错了账单。要做到工作只执行一次(exactly-once),须要在工作提交,散发,执行全链路上的每个环节都做到,包含用户在实现工作解决代码时也要在执行胜利,执行失败等各种状况下,做到 exactly-once。如何实现残缺的 exactly-once 比较复杂,超出了本文的探讨范畴。很多时候,零碎提供一个简化的语义也很有价值,即工作只胜利执行一次。工作去重须要用户在提交工作时指定工作 ID,零碎通过 ID 来判断该工作是否曾经被提交和胜利执行过。
  • 工作谬误重试:正当的工作重试策略对高效、牢靠的实现工作十分要害。工作的重试要思考几个因素:1)要匹配上游工作执行零碎的解决能力。比方收到上游工作执行零碎的流控谬误,或者感知到工作执行成为瓶颈,须要指数退却重试。不能因为重试反而加大了上游零碎的压力,压垮上游;2)重试的策略要简略清晰,易于用户了解和配置。首先要对谬误进行分类,辨别不可重试谬误,可重试谬误,流控谬误。不可重试谬误是指确定性失败的谬误,重试没有意义,比方参数谬误,权限问题等等。可重试谬误是指导致工作失败的因素具备必然性,通过重试工作最终会胜利,比方网络超时等零碎外部谬误。流控谬误是一种比拟非凡的可重试谬误,通常意味着上游曾经满负荷,重试须要采纳退却模式,管制发送给上游的申请量。
  • 工作的负载平衡:工作的执行工夫变化很大,短的几百毫秒,长的数十小时。简略的 round-robin 形式散发工作,会导致执行节点负载不均。实际中常见的模式是将工作搁置到队列中,执行节点依据本身工作执行状况被动拉取工作。应用队列保留工作,让依据节点的负载把工作散发到适合的节点上,让节点的负载平衡。工作负载平衡通常须要散发零碎和执行子系统配合实现。
  • 工作按优先级散发:工作解决零碎通常对接很多的业务场景,他们的工作类型和优先级各不相同。位于业务外围体验相干的工作执行优先级要高于边缘工作。即便同样是音讯告诉,淘宝上买家收到一个商品评论告诉的重要性必定低于新冠疫情中的核酸检测告诉。但另一方面,零碎也要放弃肯定水平的偏心,不要让高优先级工作总是抢占资源,而饿死低优先级工作。
  • 工作流控:工作流控典型的应用场景是削峰填谷,比方用户一次性提交数十万的工作,冀望在几个小时内缓缓解决。因而零碎须要限度工作的散发速率,匹配上游工作执行的能力。工作流控也是保障系统可靠性的重要伎俩,某类工作提交量忽然爆发式增长,零碎要通过流控限度其对系统的冲击,减小对其余工作的影响。
  • 批量暂停和删除工作:在理论生产环境,提供工作批量暂停和删除十分重要。用户总是会呈现各种情况,比方工作的执行呈现了某些问题,最好能暂停后续工作的执行,人工查看没有问题后,再复原执行;或者长期暂停低优先级工作,开释计算资源用于执行更高优先级的工作。另一种状况是提交的工作有问题,执行没有意义。因而零碎要能让用户十分不便的删除正在执行和排队中的工作。工作的暂停和删除须要散发和执行子系统配合实现。

工作散发的架构可分为拉模式和推模式。拉模式通过工作队列散发工作。执行工作的实例被动从工作队列中拉取工作,处理完毕后再拉取新工作。绝对于拉模式,推模式减少了一个分配器的角色。分配器从工作队列中读取工作,进行调度,推送给适合的工作执行实例。

拉模式的架构清晰,基于 Redis 等风行软件能够疾速搭建工作散发零碎,在简略工作场景下体现良好。但如果要反对工作去重,工作优先级,批量暂停或删除,弹性的资源扩缩容等简单业务场景须要的性能,拉模式的实现复杂度会迅速减少。实际中,拉模式面临以下一些次要的挑战。

  • 资源主动伸缩和负载平衡简单。工作执行实例和工作队列建设连贯,拉取工作。当工作执行实例规模较大时,对工作队列的连贯资源会造成很大的压力。因而须要一层映射和调配,工作实例只和对应的工作队列连贯。下图是 Slack 公司的异步工作解决零碎架构。Worker 节点只和局部 Redis 实例相连。这解决了 worker 节点大规模扩大的能力,然而减少了调度和负载平衡的复杂度。

  • 从反对工作优先级,隔离和流控等需要的角度思考,最好能应用不同的队列。但队列过多,又减少了治理和连贯资源耗费,如何均衡很有挑战。
  • 工作去重,工作批量暂停或者删除等性能依赖音讯队列性能,但很少有音讯类产品能满足所有需要,经常须要自行开发。例如从可扩展性的角度,通常做不到每一类工作都对应独自的工作队列。当工作队列中蕴含多种类型的工作时,要批量暂停或者删除其中某一类的工作,是比较复杂的。
  • 工作队列的工作类型和工作解决逻辑耦合。如果工作队列中蕴含多种类型的工作,要求工作解决逻辑也要实现相应的解决逻辑,对用户不敌对。在实践中,A 用户的工作解决逻辑不会预期接管到别的用户工作,因而工作队列通常由用户自行治理,进一步减少了用户的累赘。

推模式的核心思想是将工作队列和工作执行实例解耦,平台侧和用户的边界更加清晰。用户只须要专一于工作解决逻辑的实现,而工作队列,工作执行节点资源池的治理都由平台负责。推模式的解耦也让工作执行节点的扩容不再受工作队列的连贯资源等方面的限度,可能实现更高的弹性。但推模式也引入了很多的复杂度,工作的优先级治理,负载平衡,调度散发,流控等都由分配器负责,分配器须要和上下游零碎联动。

总的来说,当工作场景变得复杂后,无论拉还是推模式,零碎复杂度都不低。但推模式让平台和用户的边界更清晰,简化了用户的应用复杂度,因而有较强技术实力的团队,实现平台级的工作解决零碎时,通常会抉择推模式。

工作执行

工作执行子系统治理一批执行工作的 worker 节点,以弹性、牢靠的形式执行工作。典型的工作执行子系统需具备如下性能:

  • 工作的牢靠执行。工作一旦提交胜利,无论任何状况,零碎该当保障工作被执行。例如执行工作的节点宕机,工作该当调度到其余的节点执行。工作的牢靠执行通常是工作散发和工作执行子系统独特配合实现。
  • 共享资源池。不同类型的工作解决资源共享对立的资源池,这样能力削峰填谷,进步资源利用效率,降低成本。例如把计算密集,io 密集等不同类型的任务调度到同一台 worker 节点上,就能更充沛的利用节点上的 CPU,内存,网络等多个维度的资源。共享资源池对容量治理,工作资源配额治理,工作优先级治理,资源隔离提出了更高的要求。
  • 资源弹性伸缩。零碎能依据负载的执行状况伸缩执行节点资源,降低成本。伸缩的机会和数量十分要害。常见的依据工作执行节点的 CPU,内存等资源水位状况伸缩,工夫较长,不能满足实时性要求高的场景。很多零碎也应用排队工作数等指标进行伸缩。另一个值得关注的点是执行节点的扩容须要匹配上下游零碎的能力。例如当工作散发子系统应用队列来散发工作时,worker 节点的扩容要匹配队列的连贯能力。
  • 工作资源隔离。在 worker 节点上执行多个不同的工作时,资源是互相隔离的。通常应用容器的隔离机制实现。
  • 工作资源配额。用户的应用场景多样,经常蕴含多种工作类型和优先级。零碎要反对用户为不同优先级的工作或者处理函数设置资源配额,为高优先级工作预留资源,或者限度低优先级工作能应用的资源。
  • 简化工作解决逻辑的编码。好的工作解决零碎,可能让用户专一于实现单个工作解决逻辑,零碎主动并行、弹性、牢靠的执行工作。
  • 平滑降级。底层零碎的降级不要中断长时工作的执行。
  • 执行后果告诉。实时告诉工作执行状态和后果。对于执行失败的工作,工作的输出被保留到死信队列中,不便用户随时手动重试。

工作执行子系统通常应用 K8s 治理的容器集群作为资源池。K8s 可能治理节点,将执行工作的容器实例调度到适合的节点上。K8s 也内置了作业(Jobs)和定时作业(Cron Jobs)的反对,简化了用户应用 Job 负载的难度。K8s 有助于实现共享资源池治理,工作资源隔离等性能。但 K8s 次要能力还是在 POD/ 实例治理上,很多时候须要开发更多的性能来满足异步工作场景的需要。例如:

  • K8s 的 HPA 个别难以满足工作场景下的主动伸缩。Keda 等开源我的项目提供了按排队工作数等指标伸缩的模式。AWS 也联合 CloudWatch 提供了相似的解决方案。
  • K8s 个别须要配合队列来实现异步工作,队列资源的治理须要用户自行负责。
  • K8s 原生的作业调度和启动工夫比较慢,而且提交作业的 tps 个别小于 200,所以不适宜高 tps,短延时的工作。

留神:K8s 中的作业(Job)和本文探讨的工作(task)有一些区别。K8s 的 Job 通常蕴含解决一个或者多个工作。本文的工作是一个原子的概念,单个工作只在一个实例上执行。执行时长从几十毫秒到数小时不等。

大规模多租户异步工作解决零碎实际

接下来,笔者以阿里云函数计算的异步工作解决零碎为例,探讨大规模多租户异步工作解决零碎的一些技术挑战和应答策略。在阿里云函数计算平台上,用户只须要创立工作处理函数,而后提交工作即可。整个异步工作的解决是弹性、高可用的,具备残缺的可观测能力。在实践中,咱们采纳了多种策略来实现多租户环境的隔离、伸缩、负载平衡和流控,平滑解决海量用户的高度动态变化的负载。

动静队列资源伸缩和流量路由

如前所述,异步工作零碎通常须要队列实现工作的散发。当工作解决中台对应很多业务方,那么为每一个利用 / 函数,甚至每一个用户都调配独自的队列资源就不再可行。因为绝大多数利用都是长尾的,调用低频,会造成大量队列,连贯资源的节约。并且轮询大量队列也升高了零碎的可扩展性。

但如果所有用户都共享同一批队列资源,则会面临多租户场景中经典的“noisy neighbor”问题,A 利用突发式的负载挤占队列的解决能力,影响其余利用。

实际中,函数计算构建了动静队列资源池。一开始资源池内会预置一些队列资源,并将利用哈希映射到局部队列上。如果某些利用的流量快速增长时,零碎会采取多种策略:

  • 如果利用的流量持续保持高位,导致队列积压,零碎将为他们主动创立独自的队列,并将流量分流到新的队列上。
  • 将一些延时敏感,或者优先级高的利用流量迁徙到其余队列上,防止被高流量利用产生的队列积压影响。
  • 容许用户设置工作的过期工夫,对于有实时性要求的工作,在产生积压时疾速抛弃过期工作,确保新工作能更快的解决。

负载随机分片

在一个多租环境中,避免“破坏者”对系统造成灾难性的毁坏是零碎设计的最大挑战。破坏者可能是被 DDoS 攻打的用户,或者在某些 corner case 下正好触发了零碎 bug 的负载。下图展现了一种十分风行的架构,所有用户的流量以 round-robin 的形式平均的发送给多台服务器。当所有用户的流量合乎预期时,零碎工作得很好,每台服务器的负载平均,而且局部服务器宕机也不影响整体服务的可用性。但当呈现“破坏者”后,零碎的可用性将呈现很大的危险。

如下图所示,假如红色的用户被 DDoS 攻打或者他的某些申请可能触发服务器宕机的 bug,那么他的负载将可能打垮所有的服务器,造成整个服务不可用。

上述问题的实质是任何用户的流量都会被路由到所有服务器上,这种没有任何负载隔离能力的模式在面临“破坏者”时相当软弱。对于任何一个用户,如果他的负载只会被路由到局部服务器上,能不能解决这个问题?如下图所示,任何用户的流量最多路由到 2 台服务器上,即便造成两台服务器宕机,绿色用户的申请依然不受影响。这种将用户的负载映射到局部而非全副服务器的负载分片模式,可能很好的实现负载隔离,升高服务不可用的危险。代价则是零碎须要筹备更多的冗余资源。

接下来,让咱们调整下用户负载的映射形式。如下图所示,每个用户的负载平均的映射到两台服务器上。岂但负载更加平衡,更棒的是,即便两台服务器宕机,除红色之外的用户负载都不受影响。如果咱们把分区的大小设为 2,那么从 3 台服务器中抉择 2 台服务器的组合形式有 C_{3}^{2}=3 种,即 3 种可能的分区形式。通过随机算法,咱们将负载平均的映射到分区上,那么任意一个分区不可服务,则最多影响 1 / 3 的负载。假如咱们有 100 台服务器,分区的大小依然是 2,那么分区的形式有 C_{100}{2}=4950 种,单个分区不可用只会影响 1/4950=0.2% 的负载。随着服务器的增多,随机分区的成果越显著。对负载随机分区是一个十分简洁却弱小的模式,在保障多租零碎的可用性中起到了要害的作用。

自适应上游解决能力的工作散发

函数计算的工作散发采纳了推模式,这样用户只须要专一于工作解决逻辑的开发,平台和用户的边界也很清晰。在推模式中,有一个工作分配器的角色,负责从工作队列拉取工作并调配到上游的工作解决实例上。工作分配器要能依据上游解决能力,自适应的调整工作散发速度。当用户的队列产生积压时,咱们心愿一直减少 dispatch worker pool 的工作散发能力;当达到上游解决能力的下限后,worker pool 要能感知到该状态,放弃绝对稳固的散发速度;当工作处理完毕后,work pool 要缩容,将散发能力开释给其余工作处理函数。

在实践中,咱们借鉴了 tcp 拥塞控制算法的思维,对 worker pool 的扩缩容采取 AIMD 算法(Additive Increase Multiplicative Decrease,和性增长,乘性升高)。当用户短时间内提交大量工作时,分配器不会立刻向上游发送大量工作,而是依照“和性增长”策略,线性减少散发速度,防止对上游服务的冲击。当收到上游服务的流控谬误后,采纳“乘性缩小”的策略来,依照肯定的比例来缩容 worker pool。其中流控谬误须要满足错误率和谬误数的阈值后才触发缩容,防止 worker pool 的频繁扩缩容。

向上游的工作生产方发送背压(back pressure)

如果工作的解决能力长期落后于工作的生产能力,队列中积压的工作会越来越多,尽管能够应用多个队列并进行流量路由来减小租户之间的相互影响。但工作积压超过肯定阈值后,该当更踊跃的向上游的工作生产方反馈这种压力,例如开始流控工作提交的申请。在多租共享资源的场景下,背压的施行会更加有挑战。例如 A,B 利用共享工作散发零碎的资源,如果 A 利用的工作积压,如何做到:

  • 偏心。尽可能流控 A 而不是 B 利用。流控实质是一个概率问题,为每一个对象计算流控概率,概率越精确,流控越偏心。
  • 及时。背压要传递到零碎最外层,例如在工作提交时就对 A 利用流控,这样对系统的冲击最小。

如何在多租场景中辨认到须要流控的对象很有挑战,咱们在实践中借鉴了 Sample and Hold 算法,获得了较好的成果。感兴趣的读者能够参考相干论文。

异步工作解决零碎的能力分层

依据前述对异步工作解决零碎的架构和性能的剖析,咱们将异步工作解决零碎的能力分为以下三层:

  • Level 1:个别需 1-5 人研发团队,零碎是通过整合 K8s 和音讯队列等开源软件 / 云服务的能力搭建的。零碎的能力受限于依赖的开源软件 / 云服务,难以依据业务需要进行定制。资源的应用偏动态,不具备资源伸缩,负载平衡的能力。可能承载的业务规模无限,随着业务规模和复杂度增长,零碎开发和保护的代价会迅速减少。
  • Level 2:个别需 5-10 人研发团队,在开源软件 / 云服务的根底之上,具备肯定的自主研发能力,满足常见的业务需要。不具备残缺的工作优先级、隔离、流控的能力,通常是为不同的业务方配置不同的队列和计算资源。资源的治理比拟粗放,短少实时资源伸缩和容量治理能力。零碎不足可扩展性,资源精细化治理能力,难以撑持大规模简单业务场景。
  • Level 3:个别需 10+ 人研发团队,可能打造平台级的零碎。具备撑持大规模,简单业务场景的能力。采纳共享资源池,在任务调度,隔离流控,负载平衡,资源伸缩等方面能力齐备。平台和用户界线清晰,业务方只须要专一于工作解决逻辑的开发。具备残缺的可观测能力。

论断

异步工作是构建弹性、高可用,响应迅速利用的重要伎俩。本文对异步工作的实用场景和收益进行了介绍,并探讨了典型异步工作零碎的架构、性能和工程实际。要实现一个可能满足多种业务场景需要,弹性可扩大的异步工作解决平台具备较高的复杂度。而阿里云函数计算 FC 为用户提供了开箱即用的,靠近于 Level ß3 能力的异步工作解决服务。用户只须要创立工作处理函数,通过控制台,命令行工具,API/SDK,事件触发等多种形式提交工作,就能够弹性、牢靠、可观测齐备的形式解决工作。函数计算异步工作笼罩工作解决时长从毫秒到 24 小时的场景,被阿里云数据库自制服务 DAS,支付宝小程序压测平台,网易云音乐,新东方,分众传媒,米连等团体内外客户广泛应用。

附录

  1. 函数计算异步工作和 K8s Jobs 的能力比照。

  1. 网易云音乐 Serverless Jobs 实际,音频解决算法业务落地速度 10x 晋升
  2. 其余异步工作案例

参考链接:

[1] slack engineering:

https://slack.engineering/sca…

[2] Facebook:

https://engineering.fb.com/20…

[3] Dropbox 统计:

https://dropbox.tech/infrastr…

[4] Netflix Cosmos 平台:

https://netflixtechblog.com/t…

[5] keda:

https://keda.sh/

[6] Autoscaling Asynchronous Job Queues:

https://d1.awsstatic.com/arch…

[7] 异步工作:

https://help.aliyun.com/docum…

[8] Sample and Hold 算法:

https://dl.acm.org/doi/10.114…

[9] 网易云音乐音视频算法的 Serverless 摸索之路:

https://developer.aliyun.com/…

[10] 其它异步工作案例:

https://developer.aliyun.com/…


疾速体验,应用函数计算疾速搭建掌上游戏机

点击此处查看详情

退出移动版