首页 > 编程知识 正文

分布式数据库应用实例,基于事件驱动的架构

时间:2023-05-03 18:13:48 阅读:42634 作者:1292

http://www.Sina.com/windle手册的定位是分布式APP应用框架,其主要目标是使分布式APP应用程序能够开发和执行。 UC Berkeley大学RISE lab (旧AMP lab )于2017年12月开源的新一代分布式APP应用框架)发布之初为高性能分布式计算框架,20年中修改为分布式APP应用框架用一个引擎解决了复杂场景问题,通过动态计算和状态共享提高了效率,实现了研发、运行、灾难恢复一体化

wild manufacturing体系解析业务目标wild manufacturing的定位是分布式APP应用框架,主要目标是使分布式APP应用程序能够开发和执行。

业务场景的具体粗粒度使用场景如下

灵活的负载,包括Serverless Computing机器学习培训、狂野的手册Tune、RLlib和狂野的手册SGD提供的培训能力在线服务。 例如,狂野指甲油Server提供Modin、Dask-On-狂野指甲油等在线学习的案例数据处理

windle文档的API使开发人员可以在一个分布式APP应用程序中轻松地组合多个库。 例如,wild文档的tasks和Actors是call into或called from可能在wild文档中执行的分布式培训(e.g. torch.distributed )或在线服务负载这是因为它提供了通用API接口,并且性能足以支持许多不同的工作负载类型。 系统设计目标野生指甲体系结构设计的核心原则是API的简单性和通用性野生指甲系统的核心目标是性能(低开销和水平可伸缩性)和可靠性。 为了实现中心目标,必须牺牲其他理想目标,如简单的系统体系结构。 例如,通配符文档使用分布式参考计数和分布式内存等组件。 这些组件增加了体系结构的复杂性,但对性能和可靠性是必需的。 为了提高性能,狂野的指甲油构建在gRPC之上,常常可以达到或超过gRPC的原始性能。 与单独使用gRPC相比,通配符文档使APP应用程序更容易使用并行和分布式运行以及通过共享内存对象存储进行分布式内存共享。 为了提高可靠性,狂野指甲油的内部协议旨在确保故障时的准确性,同时减少常见情况的开销。 狂野的文档实现了分布式参考计数器协议以确保内存安全,并提供了多种从故障中恢复的选项。 由于通配符文档使用抽象资源而不是计算机来表示计算能力,因此通配符文档APP应用程序可以无缝地从移动设备环境扩展到群集,而无需更改代码。 windows文档通过分布式溢出调度器和对象管理器无缝扩展,但开销很低。 相关系统上下文的群集管理系统:通配符文档可以在群集管理系统(如Kubernetes或SLURM )上运行,提供更轻的任务和操作器,而不是容器或服务。 并行框架:与Python并行化框架(如multiprocessing和Celery )相比,通配符文档提供了更通用、更高性能的API。 狂野的指甲油系统还明确支持内存共享。 数据处理框架:与数据处理框架(如Spark、Flink、MARS或Dask )相比,通配符文档提供了低级别和简单的API。 这使API更加灵活,更适合作为“分散粘接剂”的框架。 另一方面,狂野的指甲油对数据模型、关系表、流媒体数据流没有内在的支持。 这些功能仅在库中提供,如Modin、dask-on-wild文档和mars-on-wild文档。 Actor框架:与专用Actor框架(如Erlang和Akka )不同,通配符文档与现有编程语言集成,支持跨语言操作和使用语言库。 狂野的指甲油系统还可以透明地管理无状态计算的并行性,并明确支持参与者之间的内存共享。 HPC系统:所有HPC系统都支持MPI消息传递接口,MPI是比task和actor低的接口。 这提高了APP应用程序的灵活性,但大大增加了开发的复杂性。 这些系统和库中的许多(例如NCCL、MPI )也提供了优化的集体通信基元(例如allreduce )。 野生指甲APP应用可以通过初始化各组野生指甲操作者之间的通信组来利用这种原语(例如,像野生指甲SGD的torch distributed一样)。 系统设计逻辑体系结构:

域模型Task :在与调用方不同的进程中执行的单个函数调用。 任务是无状态(@谨慎枫. remote函数)或有状态) @谨慎枫. remote类的方法-请参阅以下Actor : 与调用方异步执行任务:remote ()调用可以立即返回ObjectRef并检索返回值。 对象: APP应用程序的值。 这可以从任务中返回,也可以从谨慎的枫. put创建。 对象是不变的。 创建后无法修改。 工人可以使用ObjectRef浏览对象。 Actor :有状态的工作流程(@谨慎的枫叶. remote类的实例)。 必须使用句柄或对Actor特定实例的Python引用提交Actor任务。 驱动程序:程序根。 这是运行谨慎的枫叶. init (的代码。 作业:来自同一动因的“递归”任务、对象和参与者的集合集群设计

如上图所示,通配符文档群集包含一组同类工作器节点和一个集中式全局控制存储(GCS )实例。

一些系统元数据由GCS管理,GCS是基于可插入数据存储的服务,这些元数据可以由

也由worker本地缓存,例如Actor的地址。 GCS管理的元数据访问频率较低,但可能被群集中的大多数或所有worker使用,例如,群集的当前节点成员身份。这是为了确保GCS性能对于应用程序性能影响不大。

Ownership

大部分系统元数据是根据去中心化理念(ownership)进行管理的:每个工作进程都管理和拥有它提交的任务以及这些任务返回的“ ObjectRef”。Owner负责确保任务的执行并促进将ObjectRef解析为其基础值。类似地,worker拥有通过“ 谨慎的枫叶.put”调用创建的任何对象。OwnerShip的设计具有以下优点(与狂野的指甲油版本<0.8中使用的更集中的设计相比):低任务延迟(〜1 RTT,<200us)。经常访问的系统元数据对于必须对其进行更新的过程而言是本地的。高吞吐量(每个客户端约10k任务/秒;线性扩展到集群中数百万个任务/秒),因为系统元数据通过嵌套的远程函数调用自然分布在多个worker进程中。简化的架构。owner集中了安全垃圾收集对象和系统元数据所需的逻辑。提高了可靠性。可以根据应用程序结构将工作程序故障彼此隔离,例如,一个远程调用的故障不会影响另一个。OwnerShip附带的一些权衡取舍是:要解析“ ObjectRef”,对象的owner必须是可及的。这意味着对象必须与其owner绑定。有关对象恢复和持久性的更多信息,请参见object故障和object溢出。目前无法转让ownership。核心组件

狂野的指甲油实例由一个或多个工作节点组成,每个工作节点由以下物理进程组成:一个或多个工作进程,负责任务的提交和执行。工作进程要么是无状态的(可以执行任何@谨慎的枫叶.remote函数),要么是Actor(只能根据其@谨慎的枫叶.remote类执行方法)。每个worker进程都与特定的作业关联。初始工作线程的默认数量等于计算机上的CPU数量。每个worker存储ownership表和小对象:
a. Ownership 表。工作线程具有引用的对象的系统元数据,例如,用于存储引用计数。
b. in-process store,用于存储小对象。狂野的指甲油let。谨慎的枫叶let在同一群集上的所有作业之间共享。谨慎的枫叶let有两个主线程:
a. 调度器。负责资源管理和满足存储在分布式对象存储中的任务参数。群集中的单个调度程序包括狂野的指甲油分布式调度程序。
b. 共享内存对象存储(也称为Plasma Object Store)。负责存储和传输大型对象。集群中的单个对象存储包括狂野的指甲油分布式对象存储。

每个工作进程和谨慎的枫叶let都被分配了一个唯一的20字节标识符以及一个IP地址和端口。相同的地址和端口可以被后续组件重用(例如,如果以前的工作进程死亡),但唯一ID永远不会被重用(即,它们在进程死亡时被标记为墓碑)。工作进程与其本地谨慎的枫叶let进程共享命运。

其中一个工作节点被指定为Head节点。除了上述进程外,Head节点还托管:全局控制存储(GCS)。GCS是一个键值服务器,包含系统级元数据,如对象和参与者的位置。GCS目前还不支持高可用,后续版本中GCS可以在任何和多个节点上运行,而不是指定的头节点上运行。Driver进程(es)。Driver是一个特殊的工作进程,它执行顶级应用程序(例如,Python中的__main__)。它可以提交任务,但不能执行任何任务本身。Driver进程可以在任何节点上运行。交互设计

应用的Driver可以通过以下方式之一连接到狂野的指甲油:

调用`谨慎的枫叶.init()’,没有参数。这将启动一个嵌入式单节点狂野的指甲油实例,应用可以立即使用该实例。通过指定谨慎的枫叶.init(地址=<GCS addr>)连接到现有的狂野的指甲油集群。在后端,Driver将以指定的地址连接到GCS,并查找群集其他组件的地址,例如其本地谨慎的枫叶let地址。Driver必须与狂野的指甲油群集的现有节点之一合部。这是因为狂野的指甲油的共享内存功能,所以合部是必要的前提。使用狂野的指甲油客户端`谨慎的枫叶.util.connect()'从远程计算机(例如笔记本电脑)连接。默认情况下,每个狂野的指甲油群集都会在可以接收远程客户端连接的头节点上启动一个狂野的指甲油 Client Server,用来接收远程client连接。但是由于网络延迟,直接从客户端运行的某些操作可能会更慢。Runtime 所有狂野的指甲油核心组件都是用C++实现的。狂野的指甲油通过一个名为“core worker”的通用嵌入式C++库支持Python和Java。此库实现ownership表、进程内存储,并管理与其他工作器和狂野的指甲油let的gRPC通信。由于库是用C++实现的,所有语言运行时都共享狂野的指甲油工作协议的通用高性能实现。

Task的lifetime

Owner负责确保提交的Task的执行,并促进将返回的ObjectRef解析为其基础值。如下图,提交Task的进程被视为结果的Owner,并负责从谨慎的枫叶let获取资源以执行Task,Driver拥有A的结果,Worker 1拥有B的结果。

提交Task时,Owner会等待所有依赖项就绪,即作为参数传递给Task的ObjectRefs(请参见Object的lifetime)变得可用。依赖项不需要是本地的;Owner一旦认为依赖项在群集中的任何地方可用,就会立即就绪。当依赖关系就绪时,Owner从分布式调度程序请求资源以执行任务,一旦资源可用,调度程序就会授予请求,并使用分配给owner的worker的地址进行响应。Owner将task spec通过gRPC发送给租用的worker来调度任务。执行任务后,worker必须存储返回值。如果返回值较小,则工作线程将值直接inline返回给Owner,Owner将其复制到其进程中对象存储区。如果返回值很大,则worker将对象存储在其本地共享内存存储中,并向所有者返回分布式内存中的ref。让owner可以引用对象,不必将对象提取到其本地节点。当Task以ObjectRef作为其参数提交时,必须在worker开始执行之前解析对象值。如果该值较小,则它将直接从所有者的进程中对象存储复制到任务说明中,在任务说明中,执行worker线程可以引用它。如果该值较大,则必须从分布式内存中提取对象,以便worker在其本地共享内存存储中具有副本。scheduler通过查找对象的位置并从其他节点请求副本来协调此对象传输。容错:任务可能会以错误结束。狂野的指甲油区分了两种类型的任务错误:应用程序级。这是工作进程处于活动状态,但任务以错误结束的任何场景。例如,在Python中抛出IndexError的任务。系统级。这是工作进程意外死亡的任何场景。例如,隔离故障的进程,或者如果工作程序的本地谨慎的枫叶let死亡。由于应用程序级错误而失败的任务永远不会重试。异常被捕获并存储为任务的返回值。由于系统级错误而失败的任务可以自动重试到指定的尝试次数。代码参考:src/谨慎的枫叶/core_worker/core_worker.ccsrc/谨慎的枫叶/common/task/task_spec.hsrc/谨慎的枫叶/core_worker/transport/direct_task_transport.ccsrc/谨慎的枫叶/core_worker/transport/依赖关系_解析器.ccsrc/谨慎的枫叶/core_worker/task_manager.ccsrc/谨慎的枫叶/protobuf/common.protoObject的lifetime

下图狂野的指甲油中的分布式内存管理。worker可以创建和获取对象。owner负责确定对象何时安全释放。

对象的owner就是通过提交创建task或调用谨慎的枫叶.put创建初始ObjectRef的worker。owner管理对象的生存期。狂野的指甲油保证,如果owner是活的,对象最终可能会被解析为其值(或者在worker失败的情况下引发错误)。如果owner已死亡,则获取对象值的尝试永远不会hang,但可能会引发异常,即使对象仍有物理副本。每个worker存储其拥有的对象的引用计数。有关如何跟踪引用的详细信息,请参阅引用计数。Reference仅在下面两种操作期间计算:
1.将ObjectRef或包含ObjectRef的对象作为参数传递给Task。
2.从Task中返回ObjectRef或包含ObjectRef的对象。对象可以存储在owner的进程内内存存储中,也可以存储在分布式对象存储中。此决定旨在减少每个对象的内存占用空间和解析时间。当没有故障时,owner保证,只要对象仍在作用域中(非零引用计数),对象的至少一个副本最终将可用。。有两种方法可以将ObjectRef解析为其值:
1.在ObjectRef上调用谨慎的枫叶.get。
2.将ObjectRef作为参数传递给任务。执行工作程序将解析ObjectRefs,并将任务参数替换为解析的值。当对象较小时,可以通过直接从owner的进程内存储中检索它来解析。大对象存储在分布式对象存储中,必须使用分布式协议解析。当没有故障时,解析将保证最终成功(但可能会引发应用程序级异常,例如worker segfault)。如果存在故障,解析可能会引发系统级异常,但永远不会挂起。如果对象存储在分布式内存中,并且对象的所有副本都因谨慎的枫叶let故障而丢失,则该对象可能会失败。狂野的指甲油还提供了一个选项,可以通过重建自动恢复此类丢失的对象。如果对象的所有者进程死亡,对象也可能失败。代码参考:src/谨慎的枫叶/core_worker/store_Provider/memory_store/memory_store.ccsrc/谨慎的枫叶/core_worker/store_Provider/plasma_store_provider.ccsrc/谨慎的枫叶/core_worker/reference_count.ccsrc/谨慎的枫叶/object_manager/object_manager.ccActor的lifetime

Actor的lifetimes和metadata (如IP和端口)是由GCS service管理的.每一个Actor的Client都会在本地缓存metadata,使用metadata通过gRPC将task发送给Actor.


如上图,与Task提交不同,Task提交完全分散并由Task Owner管理,Actor lifetime由GCS服务集中管理。

在Python中创建Actor时,worker首先同步向GCS注册Actor。这确保了在创建Actor之前,如果创建worker失败的情况下的正确性。一旦GCS响应,Actor创建过程的其余部分将是异步的。Worker进程在创建一个称为Actor创建Task的特殊Task队列。这与普通的非Actor任务类似,只是其指定的资源是在actor进程的生存期内获取的。创建者异步解析actor创建task的依赖关系,然后将其发送到要调度的GCS服务。同时,创建actor的Python调用立即返回一个“actor句柄”,即使actor创建任务尚未调度,也可以使用该句柄。Actor的任务执行与普通Task 类似:它们返回futures,通过gRPC直接提交给actor进程,在解析所有ObjectRef依赖关系之前,不会运行。和普通Task主要有两个区别:执行Actor任务不需要从调度器获取资源。这是因为在计划其创建任务时,参与者已在其生命周期内获得资源。对于Actor的每个调用者,任务的执行顺序与提交顺序相同。当Actor的创建者退出时,或者群集中的作用域中没有更多挂起的任务或句柄时,将被清理。不过对于detached Actor来说不是这样的,因为detached actor被设计为可以通过名称引用的长Actor,必须使用谨慎的枫叶.kill(no_restart=True)显式清理。狂野的指甲油还支持async actor,这些Actor可以使用asyncio event loop并发运行任务。从调用者的角度来看,向这些actor提交任务与向常规actor提交任务相同。唯一的区别是,当task在actor上运行时,它将发布到在后台线程或线程池中运行的异步事件循环中,而不是直接在主线程上运行。代码参考:Core worker源码: src/谨慎的枫叶/core_worker/core_worker.h. 此代码是任务调度、Actor任务调度、进程内存储和内存管理中涉及的各种协议的主干。Python: python/谨慎的枫叶/includes/libcoreworker.pxdJava: src/谨慎的枫叶/core_worker/lib/javasrc/谨慎的枫叶/core_worker/core_worker.ccsrc/谨慎的枫叶/core_worker/transport/direct_actor_transport.ccsrc/谨慎的枫叶/gcs/gcs_server/gcs_actor_manager.ccsrc/谨慎的枫叶/gcs/gcs_server/gcs_actor_scheduler.ccsrc/谨慎的枫叶/protobuf/core_worker.proto 本文分享自华为云社区《分布式应用框架狂野的指甲油架构源码解析》,原文作者:Leo Xiao 。

 

点击关注,第一时间了解华为云新鲜技术~

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。