蚂蚁如何用 Ray 来构建大规模的在线 Serverless 平台

概要

Ant Ray Serving 是蚂蚁集团基于 Ray 研发的在线服务平台,可以将用户的 Java/Python 代码部署为分布式的在线服务,并提供伸缩、流量路由和监控等能力。使用 Ant Ray Serving,用户只需要专注于自己的业务代码逻辑,不需要理解分布式服务底层的复杂度。截至目前,Ant Ray Serving 在蚂蚁集团达到了24万核的规模,是去年规模的4倍。今年所服务的场景仍然包括模型推理、运筹优化以及 Serverless 在线计算等等。本文我们会介绍 Ant Ray Serving 在2022年的关键业务场景,以及与开源 Ray Serve 的整合工作。
image
Ray-Serving-Growth

业务场景介绍

在上一篇文章《基于 Ray 打造高可用、易扩展的在线应用》中已经介绍了 Ant Ray Serving 的架构设计,今年我们仍然沿用该架构。下面将围绕 Model Serving 和 EventBus 两个业务场景展开介绍。

Model Serving

业务痛点

首先介绍一下使用 Ray 之前的技术方案。当蚂蚁的业务需要使用模型推理服务时,一般每个场景都会需要部署多个模型来做流量对比实验。在原有方案中,每个业务场景会有一个独立的集群,集群的每一个进程中会混合部署这个场景下所有的模型。当容器的内存大小不适合部署新的模型时,集群就需要扩容内存或者新建。这个方案的好处是当这个业务场景的流量稳定时,对应集群的算力(CPU数量)不需要跟着实验流量频繁的伸缩,管理起来比较简单。

我们发现这样的技术方案存在一些问题。

问题1:隔离性

首先是隔离性问题。因为集群里的每一个进程都加载同一批模型,所以当其中一个模型出现问题时,所有的服务实例都将会受到影响。其次,加载小流量模型的实例数完全跟大流量模型的实例数相同,这个超过了实际所需,会额外浪费内存资源。

另外这些模型只提供直接进行模型推理的 HTTP 服务。当用户需要对多个模型进行聚合推理 (Model Ensembling) 的场景,原有方案在模型服务以外部署了一个统一、独立的应用来提供 Ensemble Serving 服务。所有 Ensemble 流量在这里互不隔离,同样存在互相影响的风险。

问题2:弹性

原有方案因为不需要根据实验流量频繁伸缩算力,所以只需要建设比较简单的弹性能力。但如果要彻底解决隔离的问题,就需要集群的粒度从业务场景分割到模型这个单位,这会导致集群数量增加10倍以上,运维的复杂度会远超过当前人力可以支撑的范围。

同时,原有的方案基于 K8s 部署时,K8s 对不同规格的 Pod 交付能力不同。较大的模型需要申请规格比较大的 K8s Pod,比较容易遇到扩容需要等待非常久才能完成的情况。

使用 Ray 的收益

Ray是一个高性能的分布式运行时。Ray 旨在为分布式计算提供一个通用的 API。实现这一目标的核心部分是提供简单但通用的编程抽象,让Ray完成所有分布式中基础但困难的工作(内存共享,分布式容错,扩缩容,调度,以及可观测功能等)。这种理念使得开发人员可以将 Ray 与现有的 Python 和 Java 库和系统一起使用。

在 Ray 上我们能想到一些比较好的方案,让 Ray Serving 去解决以上模型服务中的问题。

服务隔离性

我们做的第一步就是将每个模型都拆成独立的 Ray 服务去部署,这样每个模型的服务发现、流量都会自然隔离开。在通常情况下,一般一个进程也只会加载一个模型,所以不同的模型也不会互相影响。这个模式也带来了新的挑战,模型和服务一比一的策略带来了服务数量和运维复杂度同时直线上升,对我们的自动运维能力提出了更高的要求。在后面的「自动伸缩」我们会更详细的介绍下这方面的工作。

资源隔离性

其次在隔离性方面,蚂蚁基于 Ray 的 Runtime Environment,增加了在 Ray Worker Pod 内部启动嵌套 docker container 的支持。Ray Serving 通过将加载模型服务的 Actor 在 Container 中拉起,并互相隔离。

这个能力有三个好处,首先可以满足不同模型对环境镜像的不同要求,实现了极大的灵活性;其次可以支持对不同 Actor 的数据目录进行隔离,而不用 Actor 自己去处理文件路径冲突的问题;再次还可以支持以 Cgroups 来针对不同 Actor 的 CPU/Memory 等资源需求进行限制。我们在蚂蚁的不少场景都使用了 Container 的模式,并且在部分实验场景进行了文件系统以及资源隔离特性的效果验证。

自动伸缩

部署后的模型服务实例,会被托管到 Ray Serving 并执行自动扩缩容策略,来降低运维所需的人力成本。这里我们分为两层 Autoscaling 的模型,分别解决服务实例和 Ray 集群的弹性问题。

目前我们的策略以横向自动伸缩为主。模型副本数的 Autoscaling 会采用服务流量、延迟以及进程的 CPU 使用率等几个重要指标,并根据 Moving Window 算法和周期预测算法(STL/Holt-Winters)两种策略来综合决策。

Ray 集群的资源和节点数的 Autoscaling 是根据 Ray Actor Pending 的情况和配置的资Buffer 大小来动态调整 Ray 集群大小。我们想要获得的效果是新的小服务在启动时不需要等待 Ray Worker Pod 申请就可以直接启动 Actor,同时这个 Buffer 又不会太大导致集群的资源利用率不太理想。

蚂蚁使用了一个叫 Cougar 的独立优化服务来同时对服务和集群两种 Workload 进行 Autoscaling 的决策。我们的想法是把所有 Autoscaling 需要的数据都聚合到一个集中的服务中更有可能做出全局最优的决策,关于这一点我们还在探索。

聚合推理

原有的架构方案使用一个独立部署的 Java 应用来完成多个模型的 Ensemble。而由于这个服务的流量无法根据模型隔离,曾经有过个别模型突发流量导致全局收到影响的问题。

得益于非常方便的独立部署能力,以及对多语言的良好支持,Ray Serving 可以比较容易的将原有的 Java Ensemble 功能迁移到 Ray Actor 上,将 Ensembling,模型都部署到一个 Ray 服务内部。未来我们还会探索使用 Ray Serve Pipline 来完成 Ensemble 的能力。

业务规模

基于 Ray 和 Ray Serving 带来的优势,我们在模型服务中部署的规模也逐渐扩大。截止今年双11,Ant Ray Serving 在 Model Serving 场景中部署了21万核,体量相比去年增加了3.5倍左右,双11期间的吞吐峰值为137万TPS。

EventBus

EventBus 是在上一篇文章《基于 Ray 打造高可用、易扩展的在线应用》中提到的基于 Ant Ray Serving 的事件驱动 Serverless 平台。

平台架构

在该平台上,用户可以通过 Pipeline 的形式编排需要处理的数据源和数据处理的 Function。其中,数据源定义了用户 Pipeline 需要接收哪些事件,Function 则定义了如何处理数

据。EventBus 提供了多种类型的 Function 模板,例如:Filter用来进行数据过滤,Convert负责数据转换,Join 可以将来自多个数据源的数据合并,等等。用户可以按需选择使用哪些 Function 模板,并将这些 Function 编排在一个 Pipeline 中。


Eventbus-Architecture
在架构上,EventBus由 Eventing 和 Serving 两部分组成:

  • Eventing:负责对接各种数据源,将接收到的事件发送给 Serving 部分处理。这些数据源包括各类消息中间件、各类RPC等等。
  • Serving:接收到来自Eventing的事件后,按照用户编排好的pipeline进行处理,目前主要使用了 Ray Serving Java Deployment 的能力。

使用 Ant Ray Serving 将 EventBus 代码部署为在线服务非常快速方便,同时还能充分利用了Ray 上调度、分布式容错、可观测等等能力。在模型服务上提到的隔离性、自动伸缩等能力同样也可以使 EventBus 场景受益。

业务规模

截止2021年双11,EventBus 场景在 Ant Ray Serving 上部署了 656 核的服务,并首次参与了双十一大促,其中峰值阶段处理了16.8万TPS的流量,其他指标如下:

  • 单个Actor(8核)处理的流量峰值:2050TPS
  • CPU最大利用率:72.8%
  • 平均响应:49.7ms
  • P99:286ms

截止到2022年双11,EventBus 的规模扩大到了4800核左右(7倍的增长),上面运行着支付、金融、保险和营销等相关业务的在线事件处理。其中参与双十一峰值的业务量也相比去年有所增加,峰值阶段流量达到了 32.5 万TPS,其他指标如下:

  • 单个Actor(8核)处理的流量峰值:2170TPS
  • CPU最大利用率:63.6%
  • 平均响应:18ms
  • P99:90ms

在蚂蚁,越来越多的场景也在拥抱 Serverless 化的轻应用,我们预期明年在这个场景中还会有比较大的规模增长。

开源合作

在建设 Ant Ray Serving 的同时,我们还在和 Anyscale Ray Serve 团队合作,目标是将 Ant Ray Serving 融合到开源 Ray Serve 的架构中。这其中包括:在 Ray Serve 中支持 Java、跨语言部署 Deployment,以及具备灵活可扩展性的组件化能力等等。我们的目标是 Ant Ray Serving 可以在 Ray Serve 的基础上继续发展,既能利用可扩展的组件化能力满足蚂蚁内部的特定需求,又能复用社区能力满足业务,并持续贡献我们内部的实用功能。

开源贡献

目前,我们已经在 Ray Serve 中贡献了 Java 语言的支持,用户可以将 Java 代码部署为 Serve Deployment,也可以通过 Java API 来管理或访问 Serve Deployment。在这个基础上,Serve API还可以进行跨语言使用,比如:使用Python API来部署和访问Java Deployment。

Ant Ray Serving 中 Java 业务场景的占比很大,所以,Ray Serve Java 语言的支持,更有利于我们进行两者架构的融合。

内部融合

在 Ant Ray Serving 中,我们已经将 Backend 组件与 Ray Serve 的 Replica 完成了融合。2022年双11大促中,Ray Serve Replica 作为 Ant Ray Serving 的计算执行单元,覆盖了全部场景下的业务代码执行。当然,现阶段融合工作只完成了一小步,更深入的工作仍在进行中。

未来计划

接下来,我们会继续进行 Ant Ray Serving 与 Ray Serve 的架构融合。AppMaster 是 Ant Ray Serving 中每个集群作业的中控角色,管控 Proxy 和 Replica 的同时,接受 ServingKeeper 的调度。ServeController 是开源 Ray Serve 中的管控角色。在架构合并后,Ant Ray Serving 中会同时存在 AppMaster 和 ServeController,Proxy 和 Replica 全部由 ServeController 接管,AppMaster 接受 ServingKeeper 的管理、执行我们内部特有的逻辑并将管控指令下发给 ServeController。这其中还有几个关键事项:

  • 合并开源和内部的Proxy。
  • 进行插件化的设计,以便在不同的环境中灵活插拔各种类型的组件,例如:状态存储、服务发现、服务协议等等。
  • Java AppMaster 与 Python ServeController 之间会涉及到跨语言的交互,Python 的 ServeController 又需要跨语言管控 Java Proxy 和 Java Replica,这就需要设计合理的 API 和数据传输格式。

    Ray-Serving-Adapt-Ray-Serve
    在开源贡献方面,我们正在筹备两件事情:
  1. 提供 Java Ingress API,以便 Java 用户可以更好地使用 Ray Serve。
  2. 在 Ray Serve 中提供支持 C++ 语言,从而满足 C++ 业务场景。

总结

在这篇文章中,我们介绍了蚂蚁计算智能技术团队如何基于 Ray 来打造了大规模的在线服务系统 Ant Ray Serving,并且在两个比较典型的业务场景中给上层平台带来了技术受益。对蚂蚁来说,在 Ray 上打造一个在线系统仍处于一个探索的过程中,我们期望能够和 Serve Pipeline, DataSet, AIR 等其他 Ray Libraries 发生更多的化学反应,给业务带来更多的收益。

我们是蚂蚁计算智能技术部团队,我们追求开放、简单的工程师文化,用技术解决问题。期待更多的优秀工程师加入我们!如果有任何问题,欢迎随时在 Ray 的 Slack 上联系或者发送邮件到 tengweicai@gmail.com 讨论。

原文由蔡腾纬、刘洋、罗澄曦、杨潇峰于 2022-12-08发表于Ray中文社区,查看原文