解析分散式應用框架Ray架構原始碼

華為雲開發者社群發表於2021-03-15
摘要:Ray的定位是分散式應用框架,主要目標是使能分散式應用的開發和執行。

Ray是UC Berkeley大學 RISE lab(前AMP lab) 2017年12月 開源的新一代分散式應用框架(剛釋出的時候定位是高效能分散式計算框架,20年中修改定位為分散式應用框架),通過一套引擎解決複雜場景問題,通過動態計算及狀態共享提高效率,實現研發、執行時、容災一體化

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)。

系統設計

邏輯架構:

解析分散式應用框架Ray架構原始碼

領域模型

  • Task:在與呼叫者不同的程式上執行的單個函式呼叫。任務可以是無狀態的(@ ray.remote函式)或有狀態的(@ ray.remote類的方法-請參見下面的Actor)。任務與呼叫者非同步執行:.remote()呼叫立即返回一個ObjectRef,可用於檢索返回值。
  • Object:應用程式值。這可以由任務返回,也可以通過ray.put建立。物件是不可變的:建立後就無法修改。工人可以使用ObjectRef引用物件。
  • Actor:有狀態的工作程式(@ ray.remote類的例項)。 Actor任務必須使用控制程式碼或對Actor的特定例項的Python引用來提交。
  • Driver: 程式根目錄。這是執行ray.init()的程式碼。
  • Job:源自同一驅動程式的(遞迴)任務,物件和參與者的集合

叢集設計

解析分散式應用框架Ray架構原始碼

如上圖所示,Ray叢集包括一組同類的worker節點和一個集中的全域性控制儲存(GCS)例項。
部分系統後設資料由GCS管理,GCS是基於可插拔資料儲存的服務,這些後設資料也由worker本地快取,例如Actor的地址。 GCS管理的後設資料訪問頻率較低,但可能被群集中的大多數或所有worker使用,例如,群集的當前節點成員身份。這是為了確保GCS效能對於應用程式效能影響不大。

Ownership

解析分散式應用框架Ray架構原始碼

  • 大部分系統後設資料是根據去中心化理念(ownership)進行管理的:每個工作程式都管理和擁有它提交的任務以及這些任務返回的“ ObjectRef”。Owner負責確保任務的執行並促進將ObjectRef解析為其基礎值。類似地,worker擁有通過“ ray.put”呼叫建立的任何物件。
  • OwnerShip的設計具有以下優點(與Ray版本<0.8中使用的更集中的設計相比):
  1. 低任務延遲(〜1 RTT,<200us)。經常訪問的系統後設資料對於必須對其進行更新的過程而言是本地的。
  2. 高吞吐量(每個客戶端約10k任務/秒;線性擴充套件到叢集中數百萬個任務/秒),因為系統後設資料通過巢狀的遠端函式呼叫自然分佈在多個worker程式中。
  3. 簡化的架構。owner集中了安全垃圾收集物件和系統後設資料所需的邏輯。
  4. 提高了可靠性。可以根據應用程式結構將工作程式故障彼此隔離,例如,一個遠端呼叫的故障不會影響另一個。
  • OwnerShip附帶的一些權衡取捨是:
  1. 要解析“ ObjectRef”,物件的owner必須是可及的。這意味著物件必須與其owner繫結。有關物件恢復和永續性的更多資訊,請參見object故障和object溢位。
  2. 目前無法轉讓ownership。

核心元件

解析分散式應用框架Ray架構原始碼

  • Ray例項由一個或多個工作節點組成,每個工作節點由以下物理程式組成:
  1. 一個或多個工作程式,負責任務的提交和執行。工作程式要麼是無狀態的(可以執行任何@ray.remote函式),要麼是Actor(只能根據其@ray.remote類執行方法)。每個worker程式都與特定的作業關聯。初始工作執行緒的預設數量等於計算機上的CPU數量。每個worker儲存ownership表和小物件:
    a. Ownership 表。工作執行緒具有引用的物件的系統後設資料,例如,用於儲存引用計數。
    b. in-process store,用於儲存小物件。
  2. Raylet。raylet在同一群集上的所有作業之間共享。raylet有兩個主執行緒:
    a. 排程器。負責資源管理和滿足儲存在分散式物件儲存中的任務引數。群集中的單個排程程式包括Ray分散式排程程式。
    b. 共享記憶體物件儲存(也稱為Plasma Object Store)。負責儲存和傳輸大型物件。叢集中的單個物件儲存包括Ray分散式物件儲存。

每個工作程式和raylet都被分配了一個唯一的20位元組識別符號以及一個IP地址和埠。相同的地址和埠可以被後續元件重用(例如,如果以前的工作程式死亡),但唯一ID永遠不會被重用(即,它們在程式死亡時被標記為墓碑)。工作程式與其本地raylet程式共享命運。

  • 其中一個工作節點被指定為Head節點。除了上述程式外,Head節點還託管:
  1. 全域性控制儲存(GCS)。GCS是一個鍵值伺服器,包含系統級後設資料,如物件和參與者的位置。GCS目前還不支援高可用,後續版本中GCS可以在任何和多個節點上執行,而不是指定的頭節點上執行。
  2. Driver程式(es)。Driver是一個特殊的工作程式,它執行頂級應用程式(例如,Python中的__main__)。它可以提交任務,但不能執行任何任務本身。Driver程式可以在任何節點上執行。

互動設計

應用的Driver可以通過以下方式之一連線到Ray:

  1. 呼叫`ray.init()’,沒有引數。這將啟動一個嵌入式單節點Ray例項,應用可以立即使用該例項。
  2. 通過指定ray.init(地址=<GCS addr>)連線到現有的Ray叢集。在後端,Driver將以指定的地址連線到GCS,並查詢群集其他元件的地址,例如其本地raylet地址。Driver必須與Ray群集的現有節點之一合部。這是因為Ray的共享記憶體功能,所以合部是必要的前提。
  3. 使用Ray客戶端`ray.util.connect()'從遠端計算機(例如膝上型電腦)連線。預設情況下,每個Ray群集都會在可以接收遠端客戶端連線的頭節點上啟動一個Ray Client Server,用來接收遠端client連線。但是由於網路延遲,直接從客戶端執行的某些操作可能會更慢。

Runtime

  • 所有Ray核心元件都是用C++實現的。Ray通過一個名為“core worker”的通用嵌入式C++庫支援Python和Java。此庫實現ownership表、程式記憶體儲,並管理與其他工作器和Raylet的gRPC通訊。由於庫是用C++實現的,所有語言執行時都共享Ray工作協議的通用高效能實現。

解析分散式應用框架Ray架構原始碼

Task的lifetime

Owner負責確保提交的Task的執行,並促進將返回的ObjectRef解析為其基礎值。如下圖,提交Task的程式被視為結果的Owner,並負責從raylet獲取資源以執行Task,Driver擁有A的結果,Worker 1擁有B的結果。

解析分散式應用框架Ray架構原始碼

  • 提交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通過查詢物件的位置並從其他節點請求副本來協調此物件傳輸。
  • 容錯:任務可能會以錯誤結束。Ray區分了兩種型別的任務錯誤:
  1. 應用程式級。這是工作程式處於活動狀態,但任務以錯誤結束的任何場景。例如,在Python中丟擲IndexError的任務。
  2. 系統級。這是工作程式意外死亡的任何場景。例如,隔離故障的程式,或者如果工作程式的本地raylet死亡。
  • 由於應用程式級錯誤而失敗的任務永遠不會重試。異常被捕獲並儲存為任務的返回值。由於系統級錯誤而失敗的任務可以自動重試到指定的嘗試次數。
  • 程式碼參考:
  1. src/ray/core_worker/core_worker.cc
  2. src/ray/common/task/task_spec.h
  3. src/ray/core_worker/transport/direct_task_transport.cc
  4. src/ray/core_worker/transport/依賴關係_解析器.cc
  5. src/ray/core_worker/task_manager.cc
  6. src/ray/protobuf/common.proto

Object的lifetime

下圖Ray中的分散式記憶體管理。worker可以建立和獲取物件。owner負責確定物件何時安全釋放。

解析分散式應用框架Ray架構原始碼

  • 物件的owner就是通過提交建立task或呼叫ray.put建立初始ObjectRef的worker。owner管理物件的生存期。Ray保證,如果owner是活的,物件最終可能會被解析為其值(或者在worker失敗的情況下引發錯誤)。如果owner已死亡,則獲取物件值的嘗試永遠不會hang,但可能會引發異常,即使物件仍有物理副本。
  • 每個worker儲存其擁有的物件的引用計數。有關如何跟蹤引用的詳細資訊,請參閱引用計數。Reference僅在下面兩種操作期間計算:
    1.將ObjectRef或包含ObjectRef的物件作為引數傳遞給Task。
    2.從Task中返回ObjectRef或包含ObjectRef的物件。
  • 物件可以儲存在owner的程式內記憶體儲存中,也可以儲存在分散式物件儲存中。此決定旨在減少每個物件的記憶體佔用空間和解析時間。
  • 當沒有故障時,owner保證,只要物件仍在作用域中(非零引用計數),物件的至少一個副本最終將可用。。
  • 有兩種方法可以將ObjectRef解析為其值:
    1.在ObjectRef上呼叫ray.get。
    2.將ObjectRef作為引數傳遞給任務。執行工作程式將解析ObjectRefs,並將任務引數替換為解析的值。
  • 當物件較小時,可以通過直接從owner的程式記憶體儲中檢索它來解析。大物件儲存在分散式物件儲存中,必須使用分散式協議解析。
  • 當沒有故障時,解析將保證最終成功(但可能會引發應用程式級異常,例如worker segfault)。如果存在故障,解析可能會引發系統級異常,但永遠不會掛起。如果物件儲存在分散式記憶體中,並且物件的所有副本都因raylet故障而丟失,則該物件可能會失敗。Ray還提供了一個選項,可以通過重建自動恢復此類丟失的物件。如果物件的所有者程式死亡,物件也可能失敗。
  • 程式碼參考:
  1. src/ray/core_worker/store_Provider/memory_store/memory_store.cc
  2. src/ray/core_worker/store_Provider/plasma_store_provider.cc
  3. src/ray/core_worker/reference_count.cc
  4. src/ray/object_manager/object_manager.cc

 

Actor的lifetime

Actor的lifetimes和metadata (如IP和埠)是由GCS service管理的.每一個Actor的Client都會在本地快取metadata,使用metadata通過gRPC將task傳送給Actor.

解析分散式應用框架Ray架構原始碼


如上圖,與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主要有兩個區別:
  1. 執行Actor任務不需要從排程器獲取資源。這是因為在計劃其建立任務時,參與者已在其生命週期內獲得資源。
  2. 對於Actor的每個呼叫者,任務的執行順序與提交順序相同。
  • 當Actor的建立者退出時,或者群集中的作用域中沒有更多掛起的任務或控制程式碼時,將被清理。不過對於detached Actor來說不是這樣的,因為detached actor被設計為可以通過名稱引用的長Actor,必須使用ray.kill(no_restart=True)顯式清理。
  • Ray還支援async actor,這些Actor可以使用asyncio event loop併發執行任務。從呼叫者的角度來看,向這些actor提交任務與向常規actor提交任務相同。唯一的區別是,當task在actor上執行時,它將釋出到在後臺執行緒或執行緒池中執行的非同步事件迴圈中,而不是直接在主執行緒上執行。
  • 程式碼參考:
  1. Core worker原始碼: src/ray/core_worker/core_worker.h. 此程式碼是任務排程、Actor任務排程、程式記憶體儲和記憶體管理中涉及的各種協議的主幹。
  2. Python: python/ray/includes/libcoreworker.pxd
  3. Java: src/ray/core_worker/lib/java
  4. src/ray/core_worker/core_worker.cc
  5. src/ray/core_worker/transport/direct_actor_transport.cc
  6. src/ray/gcs/gcs_server/gcs_actor_manager.cc
  7. src/ray/gcs/gcs_server/gcs_actor_scheduler.cc
  8. src/ray/protobuf/core_worker.proto
本文分享自華為雲社群《分散式應用框架Ray架構原始碼解析》,原文作者:Leo Xiao 。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章