Spark——性能調優——Shuffle

一、序引
    當以分佈式方式處理數據時,常常需要執行map與reduce轉換。由於巨量數據必須從一個節點傳輸到另外的節點,給集羣中的cpu、磁盤、內存造成沉重的負載壓力,同時也會給網絡帶寬帶來壓力。所以,reduce階段進行的shuffle過程,往往是性能的瓶頸所在。
    shuffle過程涉及數據排序、重分區、網絡傳輸時的序列化與反序列化,爲了減少I/O帶寬及磁盤I/O操作,還要對數據進行壓縮。故而,shuffle的性能將直接spark的整體運算性能。
    因爲shuffle文件數(M(Map數) * R(Reduce數))將會非常大。故而,這是性能損失的關鍵所在。折衷方案:壓縮輸出文件。spark默認的是Snappy,但也支持選擇lz4、lzf。
    reduce階段,內存問題異常突出。對於一個reducer而言,被shuffle的所有數據都必須放到內存中。若內存不夠,則會因內存溢處而導致job失敗。同時,這也是爲何reducer數量如此重要的原因。reducer增加時,理應及時減少每個reducer對應的數據量。
    Hadoop與Spark的對比:hadoop中,map與reduce階段存在交疊,mapper把輸出數據推送到reducer;spark中,只有map階段結束,reduce階段才啓動工作,reducer將拉取shuffle後的數據。
    在Spark中,引入了shuffle file consolidation機制,儘量輸出少一點但大一些的文件。map階段爲每個分區輸出一個shuffle文件。shuffle文件數是每個核的reducer數,而不是每個mapper的reducer數。原有運行在相同CPU核上的map任務都將輸出相同的shuffle文件,每個reducer都有一個文件。要啓用shuffle文件合併,必須把spark.shuffle.consolidateFile=true。
二、Shuffle與數據分區
    join、reduceByKey、groupByKey、cogroup等轉換(transformation)操作需要在集羣中shuffle數據。這些操作可能需要對整個數據集進行shuffle、排序及重新分區,十分消耗性能。“預分區”(pre-partition)的方案,可以有效提高性能。
    如果RDD已分區,就能避免數據shuffle。
    對於涉及兩個或更多RDD的轉換操作,數據分區更爲重要。需要join操作的RDD越多,要處理的數據就越多,這些數據是shuffle的重要內容。
    爲進一步改善join轉換的性能,你可以用相同的分區器對兩個RDD進行分區。如此,不需要通過網絡進行shuffle。
    總之,shuffle操作異常的消耗性能,當編寫Spark應用時,應當減少Shuffle的次數及在集羣中傳輸數據。由於避免了跨節點傳輸數據引起的CPU、磁盤與網絡I/O壓力,分區得當的RDD能有效提升應用的性能。
三、算子與shuffle
    正確的時機選擇正確的算子(operator),可有效避免讓大量數據分佈到集羣各個worker節點。
    初學者傾向於用轉換來完成job,而不會思考它們背後觸發的操作。這是一個極爲重要的認識誤區。
    1、groupByKey vs reduceByKey
        groupByKey,特定鍵(Key)的所有值必須在一個任務中進行處理。爲達到這個目的,整個數據集被shuffle,特定鍵的所有單詞對被髮送到一個節點。因此,會消耗大量的時間。與此同時,可能還有“一個大數據集,一個鍵有很多值,一個任務可能會用完所有內存”的爆炸性問題。
        reduceByKey算子的函數會被應用到單臺機器上一個鍵的全部值,處理後得到的中間結果隨後會在集羣中發送。
        groupByKey: {
{(A,1),(A,1),(A,1),(B,1)},
{(A,1),(A,1),(B,1),(B,1)},
{(A,1),(B,1),(B,1)}
} →→→ {
{{(A,1),(A,1),(A,1),(A,1),(A,1),(A,1)}→(A,6)}
{{(B,1),(B,1),(B,1),(B,1),(B,1)}→(B,5)}
}
reduceByKey: {
{{(A,1),(A,1),(A,1),(B,1)}→{(A,3),(B,1)}}
{{(A,1),(A,1),(B,1),(B,1)}→{(A,2),(B,2)}},
{{(A,1),(B,1),(B,1)}→{(A,1),(B,2)}}
} →→→ {
{{(A,1),(A,1),(A,1)}→(A,6)}
{{(B,1),(B,1),(B,1)}→(B,5)}
}

        從流程圖中不難看出,reduceByKey代替groupByKey來解決聚合問題,顯著減少了需要壓縮及shuffle的數據,性能有了巨大的提升空間。
    2、repartition vs coalesce
        兩種方法,均可以實現“需要改變RDD分區數來改變並行度”的情形。
        重分區算子會隨機rshuffle數據,並將其分發到許多分區中。可能比RDD的原始分區數多,亦可能少。
        coalesce算子會得到同樣的結果,但是減少RDD的分區數能避免shuffle。而coalesce並不是總能避免shuffle。如果大幅減少分區數,將它設置成比節點數還少,那麼剩餘節點上的數據將被送到包含這些分區的其它節點上。在減少分區數時,由於僅對一部分數據而不是對整個數據集進行shuffle,因而coalesce比repartition執行的效果更好。
    3、reduceByKey vs aggregateByKey
        爲了避免reduceByKey方案中的內存分配問題,可用aggregateByKey代替。
        必須爲aggregateByKey提供三個參數:1)、零值(zero),即聚合的初始值。2)、函數f:(U,V),把值V合併到數據結構U,該函數在分區內合併值時被調用。3)、函數g:(U,U),合併兩個數據結構U。在分區間合併值時被調用。
四、Shuffle並不總是壞事
    總的來說,避免shuffle是正確選擇,但shuffle並不總是壞事。
    第一,shuffle是spark在集羣裏重新組織數據的一種方式,因而是必不可少。
    第二,有時,shuffle能節省應用執行的時間。

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