Ray架构解析
业务目标
Ray的定位是分布式应用框架,主要目标是使能分布式应用的开发和运行。
业务场景
具体的粗粒度使用场景包括
- 弹性负载,比如Serverless Computing
- 机器学习训练,Ray Tune, RLlib, RaySGD提供的训练能力
- 在线服务, 例如Ray Server提供在线学习的案例
- 数据处理, 例如Modin, Dask-On-Ray, MARS-on-Ray
- 临时计算(例如,并行化Python应用程序,将不同的分布式框架粘合在一起)
Ray的API让开发者可以轻松的在单个分布式应用中组合多个libraries,例如,Ray的tasks和Actors可能会call into 或called from在Ray上运行的分布式训练(e.g. torch.distributed)或者在线服务负载; 在这种场景下,Ray是作为一个“分布式胶水”系统,因为它提供通用API接口并且性能足以支撑许多不同工作负载类型。
系统设计目标
- Ray架构设计的核心原则是API的简单性和通用性
- Ray的系统的核心目标是性能(低开销和水平可伸缩性)和可靠性。为了达成核心目标,设计过程中需要牺牲一些其他理想的目标,例如简化的系统架构。例如,Ray使用了分布式参考计数和分布式内存之类的组件,这些组件增加了体系结构的复杂性,但是对于性能和可靠性而言却是必需的。
- 为了提高性能,Ray建立在gRPC之上,并且在许多情况下可以达到或超过gRPC的原始性能。与单独使用gRPC相比,Ray使应用程序更容易利用并行和分布式执行以及分布式内存共享(通过共享内存对象存储)。
- 为了提高可靠性,Ray的内部协议旨在确保发生故障时的正确性,同时又减少了常见情况的开销。 Ray实施了分布式参考计数协议以确保内存安全,并提供了各种从故障中恢复的选项。
- 由于Ray使用抽象资源而不是机器来表示计算能力,因此Ray应用程序可以无缝的从便携机环境扩展到群集,而无需更改任何代码。 Ray通过分布式溢出调度程序和对象管理器实现了无缝扩展,而开销却很低。
相关系统上下文
- 集群管理系统:Ray可以在Kubernetes或SLURM之类的集群管理系统之上运行,以提供更轻量的task和Actor而不是容器和服务。
- 并行框架:与Python并行化框架(例如multiprocessing或Celery)相比,Ray提供了更通用,更高性能的API。 Ray系统还明确支持内存共享。
- 数据处理框架: 与Spark,Flink,MARS或Dask等数据处理框架相比,Ray提供了一个low-level且较简化的API。这使API更加灵活,更适合作为“分布式胶水”框架。另一方面,Ray对数据模式,关系表或流数据流没有内在的支持。仅通过库(例如Modin,Dask-on-Ray,MARS-on-Ray)提供此类功能。
- Actor框架:与诸如Erlang和Akka之类的专用actor框架不同,Ray与现有的编程语言集成,从而支持跨语言操作和语言本机库的使用。 Ray系统还透明地管理无状态计算的并行性,并明确支持参与者之间的内存共享。
- HPC系统:HPC系统都支持MPI消息传递接口,MPI是比task和actor更底层的接口。这可以使应用程序具有更大的灵活性,但是开发的复杂度加大了很多。这些系统和库中的许多(例如NCCL,MPI)也提供了优化的集体通信原语(例如allreduce)。 Ray应用程序可以通过初始化各组Ray Actor之间的通信组来利用此类原语(例如,就像RaySGD的torch distributed)。
系统设计
逻辑架构:
领域模型
- Task:在与调用者不同的进程上执行的单个函数调用。任务可以是无状态的(@ ray.remote函数)或有状态的(@ ray.remote类的方法-请参见下面的Actor)。任务与调用者异步执行:.remote()调用立即返回一个ObjectRef,可用于检索返回值。
- Object:应用程序值。这可以由任务返回,也可以通过ray.put创建。对象是不可变的:创建后就无法修改。工人可以使用ObjectRef引用对象。
- Actor:有状态的工作进程(@ ray.remote类的实例)。 Actor任务必须使用句柄或对Actor的特定实例的Python引用来提交。
- Driver: 程序根目录。这是运行
ray.init()
的代码。 - Job:源自同一驱动程序的(递归)任务,对象和参与者的集合
集群设计
如上图所示,Ray集群包括一组同类的worker节点和一个集中的全局控制存储(GCS)实例。
部分系统元数据由GCS管理,GCS是基于可插拔数据存储的服务,这些元数据也由worker本地缓存,例如Actor的地址。 GCS管理的元数据访问频率较低,但可能被群集中的大多数或所有worker使用,例如,群集的当前节点成员身份。这是为了确保GCS性能对于应用程序性能影响不大。
Ownership
-
大部分系统元数据是根据去中心化理念(ownership)进行管理的:每个工作进程都管理和拥有它提交的任务以及这些任务返回的“ ObjectRef”。Owner负责确保任务的执行并促进将ObjectRef解析为其基础值。类似地,worker拥有通过“ ray.put”调用创建的任何对象。
-
OwnerShip的设计具有以下优点(与Ray版本<0.8中使用的更集中的设计相比):
- 低任务延迟(〜1 RTT,<200us)。经常访问的系统元数据对于必须对其进行更新的过程而言是本地的。
- 高吞吐量(每个客户端约10k任务/秒;线性扩展到集群中数百万个任务/秒),因为系统元数据通过嵌套的远程函数调用自然分布在多个worker进程中。
- 简化的架构。owner集中了安全垃圾收集对象和系统元数据所需的逻辑。
- 提高了可靠性。可以根据应用程序结构将工作程序故障彼此隔离,例如,一个远程调用的故障不会影响另一个。
- OwnerShip附带的一些权衡取舍是:
- 要解析“ ObjectRef”,对象的owner必须是可及的。这意味着对象必须与其owner绑定。有关对象恢复和持久性的更多信息,请参见object故障和object溢出。
- 目前无法转让ownership。
核心组件
- Ray实例由一个或多个工作节点组成,每个工作节点由以下物理进程组成:
- 一个或多个工作进程,负责任务的提交和执行。工作进程要么是无状态的(可以执行任何@ray.remote函数),要么是Actor(只能根据其@ray.remote类执行方法)。每个worker进程都与特定的作业关联。初始工作线程的默认数量等于计算机上的CPU数量。每个worker存储ownership表和小对象:
a. Ownership 表。工作线程具有引用的对象的系统元数据,例如,用于存储引用计数。
b. in-process store,用于存储小对象。 - Raylet。raylet在同一群集上的所有作业之间共享。raylet有两个主线程:
a. 调度器。负责资源管理和满足存储在分布式对象存储中的任务参数。群集中的单个调度程序包括Ray分布式调度程序。
b. 共享内存对象存储(也称为Plasma Object Store)。负责存储和传输大型对象。集群中的单个对象存储包括Ray分布式对象存储。
每个工作进程和raylet都被分配了一个唯一的20字节标识符以及一个IP地址和端口。相同的地址和端口可以被后续组件重用(例如,如果以前的工作进程死亡),但唯一ID永远不会被重用(即,它们在进程死亡时被标记为墓碑)。工作进程与其本地raylet进程共享命运。
- 其中一个工作节点被指定为Head节点。除了上述进程外,Head节点还托管:
- 全局控制存储(GCS)。GCS是一个键值服务器,包含系统级元数据,如对象和参与者的位置。GCS目前还不支持高可用,后续版本中GCS可以在任何和多个节点上运行,而不是指定的头节点上运行。
- Driver进程(es)。Driver是一个特殊的工作进程,它执行顶级应用程序(例如,Python中的
__main__
)。它可以提交任务,但不能执行任何任务本身。Driver进程可以在任何节点上运行。
交互设计
应用的Driver可以通过以下方式之一连接到Ray:
- 调用`ray.init()’,没有参数。这将启动一个嵌入式单节点Ray实例,应用可以立即使用该实例。
- 通过指定
ray.init(地址=<GCS addr>)
连接到现有的Ray集群。在后端,Driver将以指定的地址连接到GCS,并查找群集其他组件的地址,例如其本地raylet地址。Driver必须与Ray群集的现有节点之一合部。这是因为Ray的共享内存功能,所以合部是必要的前提。 - 使用Ray客户端`ray.util.connect()'从远程计算机(例如笔记本电脑)连接。默认情况下,每个Ray群集都会在可以接收远程客户端连接的头节点上启动一个Ray Client Server,用来接收远程client连接。但是由于网络延迟,直接从客户端运行的某些操作可能会更慢。
Runtime
- 所有Ray核心组件都是用C++实现的。Ray通过一个名为“core worker”的通用嵌入式C++库支持Python和Java。此库实现ownership表、进程内存储,并管理与其他工作器和Raylet的gRPC通信。由于库是用C++实现的,所有语言运行时都共享Ray工作协议的通用高性能实现。
- 参考代码:
● Core worker源码: src/ray/core_worker/core_worker.h. 此代码是任务调度、Actor任务调度、进程内存储和内存管理中涉及的各种协议的主干。
● Python: python/ray/includes/libcoreworker.pxd
● Java: src/ray/core_worker/lib/java