不成熟思考:關於RDD的一些想法

一直好奇這個所謂的“分佈式、彈性、數據集”的理念對應到代碼層面,是怎麼落地的?比如它裏面都有什麼?如何實現惰性求值?如何實現分佈式等等。

最近終~於~開始扒Spark的代碼了,有一些感悟和猜想,記錄在這裏。

RDD不是集合類

這是這篇博客的核心點:要理解RDD,不應該用Java/Scala的Collection的方式去類比,而應該是以DSL或者Fluent API的方式去類比。

雖然名字裏面帶了“數據集”幾個字,但實際上在.filter().map()這些動作的時候,甚至連存儲的元素都還沒有加載到內存中取。

而如果把它看成一個DSL的話,你在調用這些方法的時候,就是在進行聲明式編程,告訴Spark你要做什麼而不是怎麼做,然後由Spark把這個DSL翻譯成可執行的Scala代碼。比如下面這個樣子:

/**
 * RDD提供的API
 */
1. lines = ...                              // 從分佈式文件系統讀取
2. count = lines.filter(...).count()         // 過濾並統計行數

/**
 * 對應Scala的版本
 */
3. lines : List[String] = ...               // 從分佈式文件系統讀取
4. count = lines.filter(...).size()         // 過濾並統計行數

當然,上面只是提了Executor部分的邏輯,實際上還要包括Driver彙總的邏輯纔是完整的。所以說RDD就是提供了這樣的一個編程的抽象:自動的拆分任務、合併任務結果。

惰性求值

所以RDD爲什麼是惰性求值(Lazy Evaluation)。我的理解是,爲了能生成底層可執行的Scala代碼,因此只有當觸發action動作的時候,才動手構造出這樣一個執行鏈、才把這些邏輯打包放在task裏面分發給各個Executor去執行。如果不這麼做,反而更難實現。

爲了能實現惰性求值,需要幾個要素。比如我要知道“完整”的調用鏈是什麼樣子的。這裏麪包括了2個部分,一個是調用鏈什麼時候結束的,也就是action動作的發生,另一個是結束之前的調用鏈都有哪些動作,也就是每個RDD都要記住自己的血緣關係。

說道血緣關係,爲什麼說DAG會從action開始,倒着一直找到最初的那個RDD?因爲它要找到所有“有關係”的RDD才能把filter啊、map啊之類的算子裏面的函數給扒拉出來,然後重新轉成Scala的集合類能接受的形式啊。

又因爲是倒着找的,所以如果不做特殊處理(比如persist)C <- B <- A,D <- B <- A,其中A和B這倆RDD肯定是不能複用的。

至此我感覺我明白了“爲什麼說你理解了什麼是Scala的集合操作,基本就理解了什麼是Spark”這句話。

分區是什麼

一開始把RDD作爲集合的時候,覺得分區是個特別抽象的概念(尤其是repartition)。實際上RDD的分區只是一個數組,最終Job會被拆分成一個個Task,而Task的數量和分區數有關。

persist是什麼

未完待續...

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