大規模圖計算框架之GraphLite

3.C++ API

這一節主要介紹Pregel C++ API中最重要的幾個方面,暫時忽略相關其他機制。編寫一個Pregel程序需要繼承Pregel中已預定義好的一個基類——Vertex類(見圖3)。
Pregel: A System for Large-Scale Graph Processing(譯) - 星星 - 銀河裏的星星
該類的模版參數中定義了三個值類型參數,分別表示頂點,邊和消息。每一個頂點都有一個對應的給定類型的值。這種形式可能看上有很多限制,但用戶可以用protocol buffer來管理增加的其他定義和屬性。而邊和消息類型的行爲比較類似。

用戶覆寫Vertex類的虛函數Compute(),該函數會在每一個超級步中對每一個頂點進行調用。預定義的Vertex類方法允許Compute()方法查詢當前頂點及其邊的信息,以及發送消息到其他的頂點。Compute()方法可以通過調用GetValue()方法來得到當前頂點的值,或者通過調用MutableValue()方法來修改當前頂點的值。同時還可以通過由出邊的迭代器提供的方法來查看修改出邊對應的值。這種狀態的修改是立時可見的。由於這種可見性僅限於被修改的那個頂點,所以不同頂點併發進行的數據訪問是不存在競爭關係的。

頂點和其對應的邊所關聯的值是唯一需要在超級步之間持久化的頂點級狀態。將由計算框架管理的圖狀態限制在一個單一的頂點值或邊值的這種做法,簡化了主計算流程,圖的分佈以及故障恢復。

3.1 消息傳遞機制

頂點之間的通信是直接通過發送消息,每條消息都包含了消息值和目標頂點的名稱。消息值的數據類型是由用戶通過Vertex類的模版參數來指定。

在一個超級步中,一個頂點可以發送任意多的消息。當頂點V的Compute()方法在S+1超級步中被調用時,所有在S超級步中發送給頂點V的消息都可以通過一個迭代器來訪問到。在該迭代器中並不保證消息的順序,但是可以保證消息一定會被傳送並且不會重複。

一種通用的使用方式爲:對一個頂點V,遍歷其自身的出邊,向每條出邊發送消息到該邊的目標頂點,如圖4中PageRank算法(參見5.1節)所示的那樣。但是,dest_vertex並不一定是頂點V的相鄰頂點。一個頂點可以從之前收到的消息中獲取到其非相鄰頂點的標識符,或者頂點標識符可以隱式的得到。比如,圖可能是一個clique(一個圖中兩兩相鄰的一個點集,或是一個完全子圖),頂點的命名規則都是已知的(從V1到Vn),在這種情況下甚至都不需要顯式地保存邊的信息。

當任意一個消息的目標頂點不存在時,便執行用戶自定義的handlers。比如在這種情況下,一個handler可以創建該不存在的頂點或從源頂點中刪除這條邊。

3.2 Combiners

發送消息,尤其是當目標頂點在另外一臺機器時,會產生一些開銷。某些情況可以在用戶的協助下降低這種開銷。比方說,假如Compute() 收到許多的int 值消息,而它僅僅關心的是這些值的和,而不是每一個int的值,這種情況下,系統可以將發往同一個頂點的多個消息合併成一個消息,該消息中僅包含它們的和值,這樣就可以減少傳輸和緩存的開銷。

Combiners在默認情況下並沒有被開啓,這是因爲要找到一種對所有頂點的Compute()函數都合適的Combiner是不可能的。而用戶如果想要開啓Combiner的功能,需要繼承Combiner類,覆寫其virtual函數Combine()。框架並不會確保哪些消息會被Combine而哪些不會,也不會確保傳送給Combine()的值和Combining操作的執行順序。所以Combiner只應該對那些滿足交換律和結合律的操作打開。

對於某些算法來說,比如單源最短路徑(參見5.2節),我們觀察到通過使用Combiner將流量降低了4倍多。

3.3 Aggregators

Pregel的aggregators是一種提供全局通信,監控和數據查看的機制。在一個超級步S中,每一個頂點都可以向一個aggregator提供一個數據,系統會使用一種reduce操作來負責聚合這些值,而產生的值將會對所有的頂點在超級步S+1中可見。Pregel包含了一些預定義的aggregators,如可以在各種整數和string類型上執行的min,max,sum操作。

Aggregators可以用來做統計。例如,一個sum aggregator可以用來統計每個頂點的出度,最後相加就是整個圖的邊的條數。更復雜的一些reduce操作還可以產生統計直方圖。

Aggregators也可以用來做全局協同。例如, Compute()函數的一些邏輯分支可能在某些超級步中執行,直到and aggregator表明所有頂點都滿足了某條件,之後執行另外的邏輯分支直到結束。又比如一個作用在頂點ID之上的min和max aggregator,可以用來選定某頂點在整個計算過程中扮演某種角色等。

要定義一個新的aggregator,用戶需要繼承預定義的Aggregator類,並定義在第一次接收到輸入值後如何初始化,以及如何將接收到的多個值最後reduce成一個值。Aggregator操作也應該滿足交換律和結合律。

默認情況下,一個aggregator僅僅會對來自同一個超級步的輸入進行聚合,但是有時也可能需要定義一個sticky aggregator,它可以從所有的supersteps中接收數據。這是非常有用的,比如要維護全局的邊條數,那麼就僅僅在增加和刪除邊的時候才調整這個值了。

還可以有更高級的用法。比如,可以用來實現一個△-stepping最短路徑算法所需要的分佈式優先隊列[37]。每個頂點會根據它的當前距離分配一個優先級bucket。在每個超級步中,頂點將它們的indices彙報給min aggregator。在下一個超級步中,將最小值廣播給所有worker,然後讓在最小index的bucket中的頂點放鬆它們的邊。{!說明此處的核心在於說明aggregators用法,關於△-stepping最短路徑算法不再解釋,感興趣的可以參考這篇文章:Δ-Stepping: A Parallel Single Source Shortest Path Algorithm }

3.4 Topology Mutations

有一些圖算法可能需要改變圖的整個拓撲結構。比如一個聚類算法,可能會將每個聚類替換成一個單一頂點,又比如一個最小生成樹算法會刪除所有除了組成樹的邊之外的其他邊。正如用戶可以在自定義的Compute()函數能發送消息,同樣可以產生在圖中增添和刪除邊或頂點的請求。

多個頂點有可能會在同一個超級步中產生衝突的請求(比如兩個請求都要增加一個頂點V,但初始值不一樣)。Pregel中用兩種機制來決定如何調用:局部有序和handlers。

由於是通過消息發送的,拓撲改變在請求發出以後,在超級步中可以高效地執行。在該超級步中,刪除會首先被執行,先刪除邊後刪除頂點,因爲頂點的刪除通常也意味着刪除其所有的出邊。然後執行添加操作,先增加頂點後增加邊,並且所有的拓撲改變都會在Compute()函數調用前完成。這種局部有序保證了大多數衝突的結果的確定性。

剩餘的衝突就需要通過用戶自定義的handlers來解決。如果在一個超級步中有多個請求需要創建一個相同的頂點,在默認情況下系統會隨便挑選一個請求,但有特殊需求的用戶可以定義一個更好的衝突解決策略,用戶可以在Vertex類中通過定義一個適當的handler函數來解決衝突。同一種handler機制將被用於解決由於多個頂點刪除請求或多個邊增加請求或刪除請求而造成的衝突。我們委託handler來解決這種類型的衝突,從而使得Compute()函數變得簡單,而這樣同時也會限制handler和Compute()的交互,但這在應用中還沒有遇到什麼問題。

我們的協同機制比較懶,全局的拓撲改變在被apply之前不需要進行協調{!即在變更請求的發出端不會進行任何的控制協調,只有在它被接收到然後apply時才進行控制,這樣就簡化了流程,同時能讓發送更快}。這種設計的選擇是爲了優化流式處理。直觀來講就是對頂點V的修改引發的衝突由V自己來處理。

Pregel同樣也支持純local的拓撲改變,例如一個頂點添加或刪除其自身的出邊或刪除其自己。Local的拓撲改變不會引發衝突,並且頂點或邊的本地增減能夠立即生效,很大程度上簡化了分佈式的編程。

3.5 Input and Output

可以採用多種文件格式進行圖的保存,比如可以用text文件,關係數據庫,或者Bigtable[9]中的行。爲了避免規定死一種特定文件格式,Pregel將從輸入中解析出圖結構的任務從圖的計算過程中進行了分離。類似的,結果可以以任何一種格式輸出並根據應用程序選擇最適合的存儲方式。Pregel library本身提供了很多常用文件格式的readers和writers,但是用戶可以通過繼承Reader和Writer類來定義他們自己的讀寫方式。

4.Implementation

Pregel是爲Google的集羣架構[3]而設計的。每一個集羣都包含了上千臺機器,這些機器都分列在許多機架上,機架之間有這非常高的內部通信帶寬。集羣之間是內部互聯的,但地理上是分佈在不同地方的。

應用程序通常通過一個集羣管理系統執行,該管理系統會通過調度作業來優化集羣資源的使用率,有時候會殺掉一些任務或將任務遷移到其他機器上去。該系統中提供了一個名字服務系統,所以各任務間可以通過與物理地址無關的邏輯名稱來各自標識自己。持久化的數據被存儲在GFS[19]或Bigtable[9]中,而臨時文件比如緩存的消息則存儲在本地磁盤中。

4.1 Basic Architecture

Pregel library將一張圖劃分成許多的partitions,每一個partition包含了一些頂點和以這些頂點爲起點的邊。將一個頂點分配到某個partition上去取決於該頂點的ID,這意味着即使在別的機器上,也是可以通過頂點的ID來知道該頂點是屬於哪個partition,即使該頂點已經不存在了。默認的partition函數爲hash(ID) mod N,N爲所有partition總數,但是用戶可以替換掉它。

將一個頂點分配給哪個worker機器是整個Pregel中對分佈式不透明的主要地方。有些應用程序使用默認的分配策略就可以工作地很好,但是有些應用可以通過定義更好地利用了圖本身的locality的分配函數而從中獲益。比如,一種典型的可以用於Web graph的啓發式方法是,將來自同一個站點的網頁數據分配到同一臺機器上進行計算。

在不考慮出錯的情況下,一個Pregel程序的執行過程分爲如下幾個步驟:
1. 用戶程序的多個copy開始在集羣中的機器上執行。其中有一個copy將會作爲master,其他的作爲worker,master不會被分配圖的任何一部分,而只是負責協調worker間的工作。worker利用集羣管理系統中提供的名字服務來定位master位置,併發送註冊信息給master。
2. Master決定對這個圖需要多少個partition,並分配一個或多個partitions到worker所在的機器上。這個數字也可能由用戶進行控制。一個worker上有多個partition的情況下,可以提高partitions間的並行度,更好的負載平衡,通常都可以提高性能。每一個worker負責維護在其之上的圖的那一部分的狀態(頂點及邊的增刪),對該部分中的頂點執行Compute()函數,並管理髮送出去的以及接收到的消息。每一個worker都知道該圖的計算在所有worker中的分配情況。
3. Master進程爲每個worker分配用戶輸入中的一部分,這些輸入被看做是一系列記錄的集合,每一條記錄都包含任意數目的頂點和邊。對輸入的劃分和對整個圖的劃分是正交的,通常都是基於文件邊界進行劃分。如果一個worker加載的頂點剛好是這個worker所分配到的那一部分,那麼相應的數據結構就會被立即更新。否則,該worker就需要將它發送到它所應屬於的那個worker上。當所有的輸入都被load完成後,所有的頂點將被標記爲active狀態,
4. Master給每個worker發指令,讓其運行一個超級步,worker輪詢在其之上的頂點,會爲每個partition啓動一個線程。調用每個active頂點的Compute()函數,傳遞給它從上一次超級步發送來的消息。消息是被異步發送的,這是爲了使得計算和通信可以並行,以及進行batching,但是消息的發送會在本超級步結束前完成。當一個worker完成了其所有的工作後,會通知master,並告知當前該worker上在下一個超級步中將還有多少active節點。
不斷重複該步驟,只要有頂點還處在active狀態,或者還有消息在傳輸。
5. 計算結束後,master會給所有的worker發指令,讓它保存它那一部分的計算結果。

4.2 Fault tolerance

容錯是通過checkpointing來實現的。在每個超級步的開始階段,master命令worker讓它保存它上面的partitions的狀態到持久存儲設備,包括頂點值,邊值,以及接收到的消息。Master自己也會保存aggregator的值。

worker的失效是通過master發給它的週期性的ping消息來檢測的。如果一個worker在特定的時間間隔內沒有收到ping消息,該worker進程會終止。如果master在一定時間內沒有收到worker的反饋,就會將該worker進程標記爲失敗。

當一個或多個worker發生故障,被分配到這些worker的partitions的當前狀態信息就丟失了。Master重新分配圖的partition到當前可用的worker集合上,所有的partition會從最近的某超級步S開始時寫出的checkpoint中重新加載狀態信息。該超級步可能比在失敗的worker上最後運行的超級步 S’早好幾個階段,此時失去的幾個superstep將需要被重新執行{!應該是所有的partition都需要重新分配,而不僅僅是失敗的worker上的那些,否則如何重新執行丟失的超級步,也正是這樣纔有了下面的confined recovery}。我們對checkpoint頻率的選擇基於某個故障模型[13]的平均時間,以平衡checkpoint的開銷和恢復執行的開銷。

爲了改進恢復執行的開銷和延遲, Confined recovery已經在開發中。除了基本的checkpoint,worker同時還會將其在加載圖的過程中和超級步中發送出去的消息寫入日誌。這樣恢復就會被限制在丟掉的那些 partitions上。它們會首先通過checkpoint進行恢復,然後系統會通過回放來自正常的partitions的記入日誌的消息以及恢復過來的partitions重新生成的消息,更新狀態到S’階段。這種方式通過只對丟失的partitions進行重新計算節省了在恢復時消耗的計算資源,同時由於每個worker只需要恢復很少的partitions,減少了恢復時的延遲。對發送出去的消息進行保存會產生一定的開銷,但是通常機器上的磁盤帶寬不會讓這種IO操作成爲瓶頸。

Confined recovery要求用戶算法是確定性的,以避免原始執行過程中所保存下的消息與恢復時產生的新消息並存情況下帶來的不一致。隨機化算法可以通過基於超級步和partition產生一個僞隨機數生成器來使之確定化。非確定性算法需要關閉Confined recovery而使用老的恢復機制。

4.3 Worker implementation

一個worker機器會在內存中維護分配到其之上的graph partition的狀態。概念上講,可以簡單地看做是一個從頂點ID到頂點狀態的Map,其中頂點狀態包括如下信息:該頂點的當前值,一個以該頂點爲起點的出邊(包括目標頂點ID,邊本身的值)列表,一個保存了接收到的消息的隊列,以及一個記錄當前是否active的標誌位。該worker在每個超級步中,會循環遍歷所有頂點,並調用每個頂點的Compute()函數,傳給該函數頂點的當前值,一個接收到的消息的迭代器和一個出邊的迭代器。這裏沒有對入邊的訪問,原因是每一條入邊其實都是其源頂點的所有出邊的一部分,通常在另外的機器上。

出於性能的考慮,標誌頂點是否爲active的標誌位是和輸入消息隊列分開保存的。另外,只保存了一份頂點值和邊值,但有兩份頂點active flag和輸入消息隊列存在,一份是用於當前超級步,另一個用於下一個超級步。當一個worker在進行超級步S的頂點處理時,同時還會有另外一個線程負責接收從處於同一個超級步的其他worker接收消息。由於頂點當前需要的是S-1超級步的消息,那麼對superstep S和superstep S+1的消息就必須分開保存。類似的,頂點V接收到了消息表示V將會在下一個超級步中處於active,而不是當前這一次。

當Compute()請求發送一個消息到其他頂點時,worker首先確認目標頂點是屬於遠程的worker機器,還是當前worker。如果是在遠程的worker機器上,那麼消息就會被緩存,當緩存大小達到一個閾值,最大的那些緩存數據將會被異步地flush出去,作爲單獨的一個網絡消息傳輸到目標worker。如果是在當前worker,那麼就可以做相應的優化:消息就會直接被放到目標頂點的輸入消息隊列中。

如果用戶提供了Combiner,那麼在消息被加入到輸出隊列或者到達輸入隊列時,會執行combiner函數。後一種情況並不會節省網絡開銷,但是會節省用於消息存儲的空間。

4.4 Master implementation

Master主要負責的worker之間的工作協調,每一個worker在其註冊到master的時候會被分配一個唯一的ID。Master內部維護着一個當前活動的worker列表,該列表中就包括每個worker的ID和地址信息,以及哪些worker被分配到了整個圖的哪一部分。Master中保存這些信息的數據結構大小與partitions的個數相關,與圖中的頂點和邊的數目無關。因此,雖然只有一臺master,也足夠用來協調對一個非常大的圖的計算工作。

絕大部分的master的工作,包括輸入 ,輸出,計算,保存以及從 checkpoint中恢復,都將會在一個叫做barriers的地方終止:Master在每一次操作時都會發送相同的指令到所有的活着的worker,然後等待從每個worker的響應。如果任何一個worker失敗了,master便進入4.2節中描述的恢復模式。如果barrier同步成功,master便會進入下一個處理階段,例如master增加超級步的index,並進入下一個超級步的執行。

Master同時還保存着整個計算過程以及整個graph的狀態的統計數據,如圖的總大小,關於出度分佈的柱狀圖,處於active狀態的頂點個數,在當前超級步的時間信息和消息流量,以及所有用戶自定義aggregators的值等。爲方便用戶監控,Master在內部運行了一個HTTP服務器來顯示這些信息。

4.5 Aggregators

每個Aggregator(見3.3節)會通過對一組value值集合應用aggregation函數計算出一個全局值。每一個worker都保存了一個aggregators的實例集,由type name和實例名稱來標識。當一個worker對graph的某一個partition執行一個超級步時,worker會combine所有的提供給本地的那個aggregator實例的值到一個local value:即利用一個aggregator對當前partition中包含的所有頂點值進行局部規約。在超級步結束時,所有workers會將所有包含局部規約值的aggregators的值進行最後的彙總,並彙報給master。這個過程是由所有worker構造出一棵規約樹而不是順序的通過流水線的方式來規約,這樣做的原因是爲了並行化規約時cpu的使用。在下一個超級步開始時,master就會將aggregators的全局值發送給每一個worker。

參考文獻:http://blog.csdn.net/likika2012/article/details/38755069

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