Spark分佈式原理及碰到的三個坑

攝於玉泉

Spark 是繼 MP 之後的分佈式計算框架,採用分佈式架構就好比投入了更多的人力,雖然每個人做事的效率一樣,但人多了完成特定目標的時間也就短了。

一個項目要完成,除了每個人單獨完成自己的事外還需要有人來協調,比如最初給每個人分配任務,又或者彙總每個人完成的結果。

Spark 裏 executer 可以理解爲一小羣單獨做事的人,而 driver 擔任這個管理者的職務。driver 是提交任務的那臺機器,負責協調 executer 之間的數據傳輸。

在配置 Spark 任務時,我們會指定四個參數:num-executors、executor-cores、executor-memory、driver-memory。num-executors 表示投入了幾羣人,executor-cores 表示每羣人中有幾個人幹活,executor-memory 表示每羣人分配到的最大數據量,driver-memory 表示管理者最多能操作的數據量。

也就是說,Spark 任務中在底下幹活的人數是 num-executors * executor-cores。每個人當前只能處理一件事情,所以默認情況下 Spark 中 RDD 的分區數也是這麼多個。

如果自己在代碼中使用 repartition 操作,若多於幹活的人數,多的部分也只能有人空出手來再處理。從這也可以看出,假設數據較均勻的話,repatition(n) 中 n 的選擇最合理的是幹活人數的整數倍。

由於大部分數據都是在 executer 而不是在 driver 上執行的,所以一些在本地執行程序的思維會給我們帶來一些坑,我在開發 Spark 任務時就碰到過三次因此帶來的坑。

一號坑

調試 Spark 任務時有時會需要打印 rdd 類型的數據,剛開始操作時很容易就通過以下這種方式:


rddData.map(x=> println(x)).collect()

當其實println(x)這個操作是在某臺 executer 上完成的,所以打印操作也是在那臺機器上輸出。於是 driver 這臺機器上自然就得不到想要的結果。

正確的方式就是使用 take 方法把部分數據收集到 driver 機器上,再使用 print 函數。

rddData.take(5).foreach(x => println(x))

二號坑

在上一篇文章《Spark 讀取本地文件》中提到,有時我們需要從本地讀取文件。

Spark 中有一個讀取csv文件的函數

val df = spark.read.csv(fileName)

但同樣的,read這個函數是從 executer 上讀取相應文件,所以若你只是在 driver 上生成了文件程序就會報錯。

要想在 driver 上讀取本地文件可通過 scala.io.Source.FromFile 來完成,具體的可見上述文章。

三號坑

前面提到,每一個分區就會交由一個“人”來完成,所以一個分區中的數據量要與單個“人”的處理能力相匹配。

除此之外,比如使用 reduceByKey 時 driver 會協調各個分區中的數量,這就會造成 shuffle,若每個分區的數據都很少,shuffle 就會增多,這嚴重降低的效率。

比如最近我在開發一個算法時,需要用到一份商品的基礎數據。整份數據其實也就幾百兆大小,處理起來理因非常快。但在測試時卻發現四五十分鐘才能處理完。

後來發現保存爲 hdfs 的源文件的分區數有3000來個,每個分區只有幾十K的數據,所以造成 driver 協調時耗時嚴重。後來我在處理數據之前使用 repartition 命令將分區縮小到10,結果四分鐘內就程序跑完了。

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