Vineyard | 開源分佈式內存數據管理引擎

簡介: 阿里巴巴技術專家迪傑&高級開發工程師臨竹在阿里雲開發者社區特別欄目《週二開源日》直播中,介紹了Vineyard的設計動因和整體架構,並通過示例展示如何使用Vineyard來共享數據,分享Vineyard結合雲原生能力,賦能更大數據應用場景的嘗試和願景。本文爲直播內容文字整理,看直播回放,請點擊文首鏈接~

查看精彩回放:https://developer.aliyun.com/live/246411

內容簡要

一、Vineyard的創建初衷
二、Vineyard的定義及功能
三、Vineyard和雲原生結合
四、案例演示
五、Roadmap

一、Vineyard的創建初衷

(一)爲什麼需要一個分佈式內存數據管理引擎?

首先,對於單機數據計算,PyData生態已經成爲事實標準,這裏數據在不同的Python庫間0拷貝的共享非常的簡單,因爲數據都在一臺機器的內存裏,彼此間通過傳遞參數的引用就可以實現。

這裏先是聲明瞭一個numpy的array,然後另一個庫pytorch可以從這個numpy array構造一個tensor,這個過程是0拷貝。可以看到,如果我們修改了tensor的值,array的值同樣被修改了,因爲它們指向同一個進程裏的同一段內存。

那麼,如果對於更復雜的計算場景,我們需要在不同的進程或者運行時之間0拷貝的共享數據呢?
一種方法是使用Apache Arrow的Plasma,它使用進程間內存映射來實現0拷貝,但這還是在單機環境下。

對於我們業務中常見的無法放在一臺機器上的大數據,如果還要實現跨系統的0拷貝數據共享,應該如何解決呢?
Vineyard可以提供這種分佈式環境下跨系統數據0拷貝共享的能力,進一步,如果和Kubernetes結合的話,還可以產生一種更靈活的計算範式,下面我將逐步展開。

(二)現實生活中的大數據應用

  • 我們先看一個例子,如上所示,一個經典的反作弊工作流大致包含以下四個部分:
  1. 購買記錄的提取,這裏會用到一些分佈式的SQL引擎;
  2. 從原始數據構造用戶商品關係圖,並通過標籤傳播等圖算法計算節點風險,所以會引入圖計算系統進行處理;
  3. 用機器學習的方法,來提升風險檢測的精度;
  4. 對於得到的高風險用戶和商品,進行在線人工處理,這裏會用到一些數據可視化的系統。

可以看到,一個經典的大數據應用工作流中,往往會出現多種不同的計算需求,需要用到各種專業性的計算系統來逐一解決,而且往往都會使用一個分佈式文件系統,例如HDFS,來解決跨系統數據共享的問題,這也導致每個子任務只有在前序子任務完全結束以後才能被啓動。

這就帶來如下三個問題:
1) 每個專業性的計算系統想要達到生產可用都非常困難,光是開發各種數據平臺數據類型的I/O接口就要花費大量時間;
2) 使用文件系統交換數據帶來大量的I/O以及序列化和反序列化開銷;
3) 由於工作流被文件系統人爲割裂,導致全局性優化方法無法實施。

以圖計算爲例,由於圖計算的複雜性,針對各種場景下的具體問題,這些年從學界和工業界湧現了大量的針對性的圖計算系統,但是它們卻很少能達到生產可用。
這裏面一個主要原因就是光是I/O,分佈式部署,容錯機制,系統擴展性,以及各類數據源數據格式的接入就需要花費大量的時間和人力。

(三)設計初衷

針對以上三個問題,我們研發Vineyard。

首先,Vineyard不僅爲上層應用提供存儲,同時採用可擴展設計,爲上層系統提供I/O,分塊,擴容和容錯能力,從而減少上層系統在這些方面的開發工作;
其次,Vineyard採用基於共享內存的0拷貝數據共享,減少I/O和數據拷貝的開銷;
最後,Vineyard提供系統間的Stream,提升工作流的整體並行效率。

二、Vineyard的定義及功能

(一)定義

首先,Vineyard管理的是分佈式的內存中的不可變數據,也就是說,這些數據被存放在Vineyard以後,一經封存,就不再允許被更改。

這類數據在大數據應用中非常常見,一般來說,在一個大數據工作流中,上一個計算步驟結束後,它要傳給下個步驟的數據就固定了,下一個計算系統對它的使用僅僅是讀的操作。

其次,這類數據通過Vineyard傳遞給下一個步驟是0拷貝的,這是通過內存映射實現的。

同時,Vineyard還爲這些數據提供開箱即用的高層次抽象,方便計算系統對這些數據的使用,例如Graph圖、Tensor張量和DataFrame等等。

這使得上層系統可以像使用本地數據變量一樣使用Vineyard上的分佈式數據;
最後,Vineyard內建了大量的數據分塊,讀寫,檢查的驅動,同時採用可擴展設計,允許新的驅動以即插即用的形式在工作流中動態擴展Vineyard的功能,從而更好的支持上層工作流的開發和計算。

(二)系統架構

Vineyard系統架構圖

具體來看,Vineyard的系統架構如上方所示。
首先,Vineyard裏的每一個數據對象都包含數據負載和元數據兩個部分,其中數據負載保存數據本身。

例如對於一個DataFrame而言,就是每一行每一列實際的值,而元數據包含DataFrame的shape、schema,以及DataFrame在分佈式環境中是如何被切分和部署的信息等。這裏元數據可以完備的解釋數據負載,從而實現數據正確的複用。在Vineyard裏,數據負載存儲在共享內存中,而元數據通過後端的ETCD在集羣上共享。

其次,Vineyard會在集羣的每個節點上運行一個守護進程,負責這個節點上的數據負載。數據負載只能被IPC連接獲取,並且通過共享內存實現0拷貝的數據共享,而元數據還可以通過RPC連接來獲取。
這是因爲對於一個經典的並行數據處理場景,我們可以先通過RPC連接訪問元數據來獲得數據在集羣上的分佈情況,然後再分佈式的啓動一系列工作進程,並通過IPC連接來真正獲取數據負載並進行處理。
最後,Vineyard以驅動的形式,爲其存儲的數據對象提供各種內建的和即插即用的功能。

(三)案例說明

上圖爲一個分佈式DataFrame的例子。

首先我們在第一臺機器上,通過IPC域套接字連接到Vineyard,如下方所示:

然後我們通過對象ID獲得這個分佈式DataFrame,我們可以看到,它被分成多塊,每個分塊是一個單獨的DataFrame;
在這裏,這個大的分佈式DataFrame是Vineyard的一個全局對象,而這個編號爲0的分塊是一個局部對象;
通常來說,一個Vineyard全局對象,由多個局部對象組成,並且這些局部對象分佈在多臺機器上。

這裏我們可以看到,編號爲0的分塊存儲在第一臺機器上,相對於第一臺機器,它是本地數據;
相反,編號爲1的分塊數據存儲在第三臺機器上,它不是第一臺機器的本地數據。

我們繼續獲取編號爲0的分塊的數據負載,這裏數據通過共享內存映射到當前進程,實現了數據0拷貝的共享。
而編號爲1的分塊不是本地數據,我們無法獲取數據負載,但是可以獲取它的元數據。

三、Vineyard和雲原生結合

我們已經瞭解了Vineyard的基本概念,接下來我們來看Vineyard和雲原生的結合的情況,以及我們打造的全新的計算範式。

我們會先介紹如何在Kubernetes上部署Vineyard,然後介紹Vineyard如何利用Kubernetes的能力,去實現數據和工作的共同調度,從而產生一種全新的大數據計算範式。

(一)Vision: 大數據任務的雲原生範式

如上所示,還是之前的例子,在我們全新的計算範式下,它是這樣被部署的:

首先,Vineyard以Daemonset的形式部署在Kubernetes上;

其次,每個步驟的計算系統也被遷移到Kubernetes上,這使得它們獲得了動態擴展的能力;

最後,這些系統間以CRD,也就是Kubernetes上Vineyard自定義資源的形式,抽象中間數據。同時,Vineyard會利用Kubernetes的能力,協同部署中間數據和工作進程,確保每個工作進程都能獲得它需要的數據,從而實現計算工作流的高效運行。

(二)在Kubernetes上部署Vineyard

目前Vineyard已經可以通過Helm來安裝。

同時,Kubernetes上的數據共享是這樣實現的:

首先,每個節點上的Vineyard服務Pod和其他工作Pod依然通過IPC連接來實現內存共享。
在Kubernetes環境中,域套接字可以通過被掛載到Pod中的hostPath或者PVC在不同Pod之間共享。
而對於Vineyard服務容器和工作容器被綁定在同一個Pod的情形,可以通過一個emptyDir類型的Volume在兩個容器之間共享。

(三)Kubernetes的能力: CRDs

在使用Kubernetes的能力方面,首先Vineyard存儲的對象會被抽象爲Kubernetes的自定義資源,這個CRD中包含其對應對象的元數據。

同時,如果是局部對象,也就是比如剛剛說到的一個分佈式DataFrame的某個分塊,還會包含位置信息,也就是這個分塊存儲在哪個節點上。。

(四)Kubernetes的能力:協同調度數據和工作負載

由於局部對象的CRD中包含位置信息,我們可以利用它來協同調度數據和工作負載:

  1. 在工作負載Pod裏描述這個工作需要的Vineyard對象的ID(通常是一個全局對象);
  2. 通過Kubernetes的scheluder-plugin來增強調度器的調度能力,使得當工作Pod被調度時,調度器會先通過CRD查看它需要的Vineyard對象所包含的局部對象的位置信息,從而優先將工作Pod調度到相應的節點上。
  3. 如果因爲種種原因,無法調度成功,數據遷移則會被自動觸發,從而保證工作Pod總是能夠訪問到它需要的數據。

四、案例演示

下面,我們通過一個Demo來展示Vineyard如何藉助Scheduler Plugin實現數據和工作的協同調度。

這裏使用一個簡化版本來做Demo,我們會針對作弊交易數據構造一個作弊交易的分類器,在這個Demo裏面我們會用Pandas和Mars分別在單機和分佈式的情況下來預處理數據,同時用Pytorch訓練一個作弊交易的分類器。

好,我們先來看看數據。如上所示,通常情況下Vineyard以及它上面的系統用到的數據都是非常大規模的數據,這裏我們用一個小數據來做Demo,否則的話光是加載這個數據就要花費大量的時間。

這裏有三張表,分別是用戶表、商品表,還有交易表。用戶表和商品表主要包含用戶和商品的ID,以及它們各自Feature的向量。而這個交易表每一條記錄表示一個用戶購買一個商品,還有一個標籤Frod來標識這條記錄是否是一個作弊的交易,同時一些關於這些交易的Feature也都存在這個表裏面。

對於這樣一個問題,我們在單機的情況下是如何實現的呢?我們看一下 Pandas版本的代碼,如下所示。

首先我們會加載數據,用Pandas讀這三個文件得到三個DataFrame,然後把這三個DataFrame合併成一個大的Dataset,這裏用到了DataFrame的Join,這是第一步。

第二步我們通過Dataset來訓練模型,通過Dataset來構造Feature數據和Label數據。

然後使用一個最簡單的線性模型來訓練分類器,這是單機版本的實現。
如果數據非常大,無法在一臺機器上存儲的話,那麼這個單機版本的代碼顯然是無法實現這樣的功能,因爲Pandas甚至都無法去讀這樣的一個文件,這種情況下我們如何實現呢?
這裏我們使用Mars來做第一步DataFrame處理的工作。

Mars是集團開源的一個項目,支持大規模DataFrame的計算。

對於一個Mars中的DataFrame分類,它和Vineyard類似,包含多個分塊,並且分佈在多臺機器上,所以這裏可以先用Vineyard將文件讀成一個GlobalDataFrame,然後直接加載爲Mars的一個DataFrame。

同時Mars支持DataFrame的計算,比如Join操作。Mars通過多臺機器上多個分塊間的並行計算,實現分佈式DataFrame的交易操作。最後,Mars合併的大的DataFrame會以Global DataFrame的形式存進Vineyard。

第二步使用Distributed Pytorch來進行計算。這裏Distributed Pytorch要從Vineyard裏Log出GlobalDataFrame,這個Distributed Pytorch會起多個工作進程,每個工作進程會得到相應的分塊。

首先會建立一個Vineyard Client連接到VINEYARD_IPC_SOCKET,然後獲得本地分塊的ID,隨後Client會拿到這些數據負載,並且把它們合併成一個總的表,並且通過總表和之前一樣構建Pytorch Dataset,接下來訓練的內容就和剛剛一致了。

上圖爲實際執行Demo過程。

首先我們有一個乾淨的Kubernetes環境,接着用Helm安裝Vineyard,安裝了Vineyard以後得到了兩個CRD,一個是全局對象,一個是本地對象。Vineyard在集羣上運行是以Demon Set的形式,也就是說每個Log上都有一個Vineyard Pod。

上圖爲Run Mars,Mars會啓動一個集羣,然後分佈式地做這個計算,得到一個GlobalDataFrame與它的ID。

同樣的,在Kubernetes裏面有一個和GlobalDataFrame對應的全局對象,這個全局對象包含多個分塊,也就是多個局部對象。可以看到全局對象分佈在192和193兩臺機器上,可以登錄192查看其中一個分塊具體長什麼樣。

通過Import Vineyard然後建立一個IPC Socket Client,然後選取一個192上面的一個分塊來Get。

可以看到我們得到了DataFrame,它是我們剛剛大的DataFrame的一個分塊。

我們再試圖get一下193上面的分塊,可以看到我們無法拿到這個分塊,所以如果我們的工作Pod在被調度的時候,沒有和數據所在的節點對齊,就會發生無法獲取數據的問題。應該如何解決呢?我們看一下Pytorch的Yaml。

在InitContainers裏面我們增加了一些內容,它可以確保當數據和工作負載沒有被對齊的時候,數據遷移會自動地被觸發,下面我們來Run Pytorch。

首先查看全局對象並拿到 ID,然後把ID輸入給要Run 的Pytorch。
Pytorch部署到Kubernetes後,可以看到其中兩個Pod運行在188和187上,和剛剛的192、193沒有對齊,那麼這裏會發生什麼呢?我們看一下worker-0的Log,可以看到數據被自動地遷移了,但是數據遷移的過程是很花費時間的。

這裏由於數據遷移,我們會產生重複的局部對象,比如31a也被複制了,它從193被複制到了188,從而我們的Worker可以使用它。

可以看到,數據遷移的過程不僅會花費時間,而且還會浪費內存,所以我們應該想辦法去儘量減少這種數據遷移的發生,這也就是爲什麼我們要用Scheduler Plugin來增強Kubernetes的調度能力,從而減少這種數據遷移的發生。

下面把環境清理一下,重新Run以上過程,不過這一次會使用Scheduler Plugin。

重新安裝Vineyard,重新再執行一遍Mars,又得到了一個GlobalDataFrame,那麼我們接下來檢查一下,全局對象所包含的局部對象在我們集羣中的位置。

可以看到,它依然是在192和193這兩臺機器上,接下來要安裝Scheduler Plugin。

Scheduler Plugin會根據工作負載所需要的Vineyard對象的位置來調度這個工作的Pod。後面會通過觀察Scheduler Plugin的Log來查看這個過程。

再次打開Pytorch的Yaml,可以看到我們把需要的Vineyard對象的ID寫在了這裏,Scheduler Plugin可以通過讀取這個Spec來知道工作負載是需要哪一個Vineyard對象。

我們運行Pytorch拿到 ID,Set Scheduler Plugin,可以看到兩個Pod已經運行在192和193上面,接着我們去看一下Scheduler Plugin的Log,可以看到Scheduler Plugin給193和192兩臺機器打了最高分。回到Worker-0的Log,可以看到沒有數據遷移發生,也沒有重複的局部對象被創建,是因爲沒有進行任何的數據遷移。

在這裏我們通過Scheduler Plugin成功地將數據和工作負載對齊,進行協同地調度,從而實現了高效的並行計算。
以上是Vineyard利用Scheduler Plugin實現協同調度的Demo,這爲我們打造基於Vineyard和Kubernetes的高效且靈活的大數據計算範式,打開了一扇大門。

五、Roadmap

(一)項目狀態

目前,Vineyard在GitHub已經收穫375顆星,同時有6位同事在維護這個項目,我們期待獲得來自社區的更多的支持,也希望有社區成員可以逐步成爲Vineyard的維護者。

(二)Roadmap

  • 當前:

1) 通過Github Actions構建和測試;
2) 支持各種數據類型,如數組、圖形,並支持多種計算引擎,如pytorch, mars, GraphScope;
3) 在DockerHub和Quay上釋放;
4) 與Helm進行集成。

  • 未來:

1) Vineyard Operator for Kubernetes;
2) 性能改進;
3) 更多語言SDK,如Java, Go等;
4) 存儲層次結構;
5) Scheduler-plugin:Fluid
在未來的規劃中,我們首先會在Kubernetes上做功能性更強的Vineyard Operator,比如檢查點,容錯等。此外,我們還會和集團的Fluid合作,進一步優化調度能力。

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章