天天看點

使用DataFlow表達ControlFlow的一些思考

一、控制流

從接觸面向過程語言開始,使用控制流程式設計的概念已是司空見慣。

if (condition) {
  // do something
} else {
  // do something else
}
           

分支和循環是最常見的控制流形式。由于控制條件的存在,總有一部分代碼片段會執行,另一部分不會執行。

在控制流中,想要進行資料傳遞,最關鍵的是借助于變量儲存中間狀态。是以,控制流程式設計看起來是将資料嵌套在控制流内的程式設計方式。

使用變量儲存程式狀态有個很大的優勢。通過變量緩存,可以将程式設計任務劃分為不同的階段,每個階段隻需要完成一部分功能子邏輯即可,這大大降低了複雜流程的思維成本。

但同時,也有一個比較大的劣勢,就是在分布式處理環境下,中間狀态的維護一直是一個很繁瑣的問題。這從另一個方面加大了程式設計的成本。

二、資料流

而資料流程式設計的概念最初可以探尋到函數式程式設計語言,以及靈感源于此的FlumeJava類系統(如Spark、Flink等)的程式設計API。

rdd.map(lambda).filter(lambda).reduce(lambda);
           

這種類似管道流水線形式的程式設計接口,每次處理的資料是清單形式的(LISP)。當然,這些清單放在分布式環境下換了一個新的名詞——分布式資料集(RDD/DataSet)。

資料流程式設計最大的特點是抽象了豐富的算子,通過UDF為算子指定使用者處理邏輯。是以,資料流程式設計其實蘊含了控制流嵌套在資料流内的程式設計方式。

使用資料流程式設計最大的優勢就是無需使用變量維護計算中間狀态,另外基本的清單資料格式天然滿足分布式資料存儲的要求。這也是函數式語言在自我宣傳時比較注重的一個優勢:對并行計算支援得更好。

不過,資料流程式設計的方式也并不是完美。由于事先規劃好的流水線結構,導緻了資料處理無法自主地選擇流水線分支進行處理。是以,有時候看似很簡單的控制邏輯,使用資料流表達時就顯得比較繁瑣。

三、資料流表達的控制流

例如:下面的控制流程使用控制流程式設計很好表達。

使用DataFlow表達ControlFlow的一些思考
if (arg > MAX) {
  vertices = vertices.map(lambda);
} else {
  vertices = vertices.filter(lambda);
}
return vertices;
           

這裡的參數arg可能來源于使用者輸入,或者Spark/Flink driver提供的變量。這種使用driver的單機控制流全局統籌的方式好像是解決了資料流選擇選擇流水線管道的目的,但是實際上這是通過重新送出新任務的方式完成的。即條件為真時,才會送出true分支内的計算任務,否則送出false分支的計算任務。

如果不借助于driver,該如何表達類似的分支控制流程呢?

使用DataFlow表達ControlFlow的一些思考

假定參數arg的類型也是分布式資料集類型DataSet<Integer>,它可能來源于上遊流水線的中間結果,那麼表達分支控制流計算可能需要如下類似方式:

// 條件資料集
DataSet<Boolean> condition = arg.map(v -> v > MAX);

// 資料集 true/false 分離
DataSet<Tuple2<Vertex, Boolean>> labelVs = vertices.join(condition);
DataSet<Vertex> trueVs = labelVs.filter(v -> v.f1).map(v -> v.f0);
DataSet<Vertex> falseVs = labelVs.filter(v -> !v.f1).map(v -> v.f0);

// 各自分支處理
trueVs = trueVs.map();
falseVs = falseVs.filter();

return trueVs.union(falseVs);
           

這裡通過将參數DataSet與輸入資料集vertices做join,然後分離(按條件true/false filter)出兩個新的資料集trueVs和falseVs。當條件為true時,trueVs就是原始資料集vertices,而falseVs為空資料集,反之則反。然後後續隻要分别對這兩個資料集做相應的處理,最後把處理結果union合并起來就達到了目的。

通過這樣的方式,實際上是同時執行了條件的true和false的分支邏輯,隻不過任何時候總有一個分支的流水線上的資料集為空罷了。

四、思考

通過前面的讨論,可以得到一些比較明顯的結論:

  • 控制流天然擅長描述控制邏輯,不過使用變量緩存中間結果不利于分布式計算抽象。
  • 資料流天然對分布式并行計算支援良好,但是在描述控制邏輯時顯得十分乏力。

在計算程式設計語言設計領域,對控制流和資料流的讨論不絕于耳。如何讓開發者更好的操縱這兩類概念也在不斷地探索,要不然也不會出現面向過程和函數式程式設計等各種程式設計範式。

而目前主流的計算系統,如Flink、Spark等,基本上處于使用driver的概念表達控制流,使用算子連接配接資料流這樣的模式。不過這都是建立在driver通過全局collect操作,将資料集的資料拉取到driver基礎之上的。本質上是driver根據條件分支的運作時結果,重新送出任務而已,這稱不上一個精彩的設計。因為,它并沒有做到讓資料流具備自主選擇流水線的能力。

那如何讓資料流具備自主選擇流水線的能力呢?說白了,自主選擇流水線,本質上是擁有任務運作時修改任務執行計劃的能力,也就是所謂的動态DAG。Ray的設計中,函數是基本的任務排程單元,而非将UDF連接配接起來的DAG,或許這種底層的任務抽象能力對于表達動态DAG的能力具有更大的優勢。

詳細了解Ray的設計,可以參考文章:高性能分布式執行架構——Ray

我的部落格即将同步至騰訊雲+社群,邀請大家一同入駐。

作者:Florian

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則作者保留追究法律責任的權利。

若本文對你有所幫助,您的

關注

推薦

是我分享知識的動力!

繼續閱讀