MapReduce實現之Reduce端重分割槽Join操作最佳化!

大資料頻道發表於2018-11-07

在前一篇文章中(連結參加文末),我們介紹了map端Join操作的幾大方法。一般情況下,我會推薦企業選擇map端的Join操作,這可以節省不小的成本。但是,如果資料集過於龐大以至於沒有合適的map端連線方法適用,則需要使用MapReduce中的shuffle對資料進行排序和連線,並考慮選擇Reduce端的Join操作。

一、重分割槽Join操作(Reduce端)

本文介紹的第一種方法是最基本的重分割槽Join操作,該方法允許執行內部和外部Join。開始之前,我們先搞清楚要解決的問題是將大型資料集Join在一起,我們選用的解決方案是Reduce端重分割槽Join。該方法是一種Reduce端Join實現,利用MapReduce的sortmerge將記錄組合在一起,作為單個MapReduce作業實現,可支援N路連線,其中N是要連線的資料集數量。

Map端負責從資料集中讀取資料,確定每個Join操作的value,並將該value的key輸出,輸出key包含在reducer中並將資料集組合在一起以生成最終結果。

單個reducer呼叫接收map函式Join操作發出的Key對應的所有值,並將資料分N個分割槽,其中N是要連線的資料集數量。reducer讀取連線value的所有輸入並將它們分割槽到記憶體中,然後跨所有分割槽執行笛卡爾積,併發出每個Join操作的結果。

圖6.10 重分割槽Join操作的基本MapReduce實現

MapReduce程式碼要支援這種技術,需要滿足以下條件:

  • 支援多個map類,每個map類處理不同的輸入資料集,這是透過使用MultipleInputs類完成的。

  • 需要一種方法來標記mapper發出的記錄,以便可以與其原點的資料集相關聯,本文將使用htuple專案處理MapReduce中的資料。

重分割槽Join操作的程式碼如下:

可以使用以下命令執行作業並檢視輸出:

總結

Hadoop捆綁了一個hadoop-datajoin模組,這是一個重分割槽Join操作框架,包括用於處理多個輸入資料集和執行Join操作的管道。上述操作示例及hadoop-datajoin程式碼是重分割槽Join的最基本形式,兩者都要求在執行笛卡爾積之前將連線key的所有資料載入到記憶體中,但如果連線key的基數大於可用記憶體,那麼,這種方法就不太適用。下一個技術將著眼解決此問題。

二、最佳化重分割槽Join操作

舊版重分割槽Join操作實現會浪費大量空間,需要將給定key的所有value載入到記憶體中才能執行多路連線,將較小的資料集載入到記憶體中才能迭代更大的資料集,沿途執行Join更有效。

我們希望在MapReduce中執行重分割槽Join,且無需快取reducer中的所有記錄。最佳化後的重分割槽Join框架將僅快取要連線的其中一個資料集,以減少reducer中快取的資料量。此最佳化僅快取來自兩個資料集中較小者的記錄,以減少快取所有記錄的記憶體開銷,圖6.11顯示了改進的重分割槽Join實現。

圖6.11 重分割槽Join操作最佳化MapReduce實現

該技術與舊版相比存在一定差異,此處使用輔助排序確保來自較小資料集的所有記錄在較大資料集的記錄之前到達reducer,以此來儘可能減少reducer中要快取的資料量。此外,mapper會發出需要進行Join操作的使用者名稱元組的key以及標識原始資料集的欄位。

以下程式碼顯示了一個新的列舉,顯示了使用者mapper如何填充元組欄位:

需要更新MapReduce驅動程式程式碼以指示元組中的哪些欄位應用於排序、分割槽和分組:

  • 分割槽程式應僅基於使用者名稱進行分割槽,以便使用者的所有記錄都到達同一個reducer。

  • 排序應使用使用者名稱和資料集指示符,以便首先排序較小的資料集(由於USERS常量小於USER_LOGS常量,導致使用者記錄在使用者登入之前排序)。

  • 分組應對使用者進行分組,以便將兩個資料集都流式傳輸到同一個reducer呼叫:

最後,我們要修改reducer以快取傳入的使用者記錄,然後將其與使用者日誌Join:

可以使用以下命令來執行作業並檢視輸出:

Hive

在執行重分割槽Join操作時,Hive可支援類似最佳化。Hive可快取Join鍵的所有資料集,然後流式傳輸大型資料集,使其不需要儲存在記憶體中。假定在查詢時,Hive最後指定的資料集最大。想象一下,你有兩個名為users和user_logs的表,而user_logs要大得多。要連線這些表,我們需要確保user_logs表被引用為查詢中的最後一個:

如果不想重新查詢,可以使用STREAMTABLE提示告訴Hive哪個表更大:

總結

此操作實現透過僅緩衝較小資料集的value來改進早期技術,但它仍然存在資料在map和reducer之間的傳輸問題,這是一個昂貴的網路成本。此外,舊版可以支援N路連線,但是這種實現僅支援雙向連線。

三、使用Bloom過濾器來減少混洗資料

如果希望根據某些謂詞對資料子集執行Join操作,例如“僅限居住在加利福尼亞地區的使用者”。到目前為止,我們還必須在reducer中執行過濾器才可以實現這一目的 ,因為只有一個資料集存放了有關狀態的詳細資訊——使用者日誌沒有該資訊。接下來,我將介紹如何在map端使用Bloom過濾器,這會對作業執行時間產生很大影響。我要解決的問題是在重分割槽Join操作中過濾資料,但要將該過濾器推送到mapper。一個可行的解決方案是使用預處理作業建立Bloom過濾器,然後在重分割槽作業中載入Bloom過濾器以過濾mapper中的記錄。

Bloom過濾器是一種非常有用的隨機資料結構,它利用位陣列簡潔表明集合,並能判斷一個元素是否屬於該集合。然而,與Java中的HashSet相比,Bloom需要的記憶體要少得多,因此它們非常適合處理大型資料集。此解決方案有兩個步驟,一是執行作業來生成Bloom過濾器,該過濾器將對使用者資料進行操作,並由居住在加利福尼亞地區的使用者填充;二是在重分割槽Join操作中使用此Bloom過濾器丟棄不需要的使用者,該過程需要Bloom過濾器的原因是使用者日誌的mapper沒有狀態的詳細資訊。

圖6.12 在重分割槽Join中使用Bloom過濾器的兩步過程

第1步:建立Bloom過濾器

第一個作業是建立Bloom過濾器,其中包含加利福尼亞州的使用者名稱。mapper生成中間Bloom過濾器,reducer將其組合成一個Bloom過濾器,作業輸出是包含序列化Bloom過濾器的Avro檔案:

第2步:重分割槽Join

重分割槽Join與上文提到的唯一區別是mapper載入第一步中生成的Bloom過濾器,並且在處理map記錄時,執行針對Bloom過濾器的元素審查以確定是否應將記錄傳送給reducer。以下程式碼顯示了兩件事:一般化Bloom過濾器載入、抽象mapper以及支援兩個Join資料集的子類:

以下命令執行兩個作業並轉儲Join輸出:


總結

該技術提出了一種在兩個資料集上執行map端過濾的有效方法,以最小化mapper和reducer之間的網路I/O。作為shuffle的一部分,它還減少了mapper和reducer的磁碟溢位資料量。過濾器通常是加速和最佳化作業最簡單有效的方法,重分割槽Join也同樣適用於其他MapReduce作業。

四、reducer端Join操作可能發生資料傾斜

資料傾斜是實際操作中很容易碰到的問題,可能存在兩種型別的資料傾斜:

  • 高Join-key基數,其中有一些連線key在一個或兩個資料集中具有大量記錄,我把這種稱之為join-product偏差。

  • 糟糕的雜湊分割槽,少數reducer在總記錄數中佔很大比例,我將此稱為雜湊分割槽傾斜。

五、加入具有高連線金鑰基數的大型資料集

這種技術解決了join-product的傾斜問題,下一個技術檢查了雜湊分割槽偏差。現在面臨的問題是某些連線key是高基數的,這會導致某些reducer在嘗試快取這些key時耗盡記憶體。我們可以過濾掉這些key並將它們單獨連線或將其溢位到reducer中並安排後續作業Join。

如果提前知道了哪些Key是高基數的,則可以將其分成單獨的Join作業,如果不確定高基數Key是哪些,則可能需要在reducer中構建智慧檢測並將其寫入副本檔案,該檔案由後續作業Join,如圖6.14所示。

圖6.13 提前知道高基數金鑰時處理傾斜

圖6.14 提前知道高基數金鑰處理時的偏差

Hive

Hive支援類似於第二種方法的偏斜緩解策略,執行作業之前可指定以下配置啟用:

可以選擇設定一些其他配置來控制在高基數key上執行的map端連線:

最後,如果在SQL中使用GROUP BY,可能還需要考慮啟用以下配置來處理分組資料中的偏差:

總結

此技術假設給定的Join鍵,只有一個資料集具有高基數出現,因此可快取較小資料集的map端連線。如果兩個資料集都是高基數的,那麼將面臨一個昂貴的笛卡爾積運算,執行起來會很慢,因為它不適合MapReduce的工作方式(這意味著它本身不可拆分和可並行化)。在這種情況下,我們應該重新檢查是否有任何技術(如過濾或投影)可幫助減少執行join所需的時間。

六、處理由雜湊分割槽生成的偏差

MapReduce的預設分割槽程式是一個雜湊分割槽程式,接受每個map輸出key的雜湊,並對reducer數量建模,以確定key被髮送到哪個reducer。雜湊分割槽程式可以很好地用作通用分割槽程式,但是有些資料集可能會導致雜湊分割槽程式因一些不成比例的金鑰雜湊到同一個reducer而使其過載。與大多數reducer相比,這些reducer需要更長時間才能完成。此外,當檢查straggler reducer計數器時,會注意到傳送給落後者的組數遠遠高於已完成的其他組。

區分高基數key與雜湊分割槽引起的偏差可以使用MapReduce reducer來識別資料傾斜型別。由效能較差的雜湊分割槽器引入的偏差將具有更多的組(唯一金鑰)傳送到這些reducer,而導致傾斜的高基數金鑰可以透過所有reducer中大致相等數量的組來證明,傾斜越多,reducer的記錄數量越多。

我們要解決的問題是reducer端連線需要很長時間才能完成,而落後的組需要比大多數reducer更長時間。使用範圍分割槽程式或編寫自定義分割槽程式,將偏移的key集中到一組reducer。此解決方案的目標是省去預設的雜湊分割槽程式,並將其替換為可以更好處理資料傾斜的內容,本文提供兩個選項可供探索:

  • 使用與Hadoop捆綁在一起的sampler和TotalOrderPartitioner,將雜湊分割槽程式替換為範圍分割槽程式。

  • 編寫自定義分割槽程式,將具有資料傾斜的key路由到為傾斜key保留的Reducer。

範圍分割槽法

範圍分割槽根據預定義值分配map輸出,其中每個map接收該範圍內的所有reducer,這正是TotalOrderPartitioner的工作原理。實際上,TeraSort使用TotalOrderPartitioner在所有Reducer之間均勻分佈,以最大限度減少資料傾斜。TotalOrderPartitioner附帶取樣器,可對輸入資料進行取樣並將其寫入HDFS,然後在分割槽時由TotalOrderPartitioner使用。

自定義分割槽法

如果已經知道哪些Key顯示資料傾斜,並且該組Key是靜態的,則可以編寫自定義分割槽程式以將這些高基數key推送到一組reducer。

總結

在上述兩種方法中,範圍分割槽可能是最佳解決方案,因為大多數情況下,我們可能不知道哪些Key是傾斜的,並且表現出傾斜的key也可能隨時間而變化。MapReduce中可能有reducer端連線,因為它們將map輸出Key排序並關聯在一起。在之後的文章中,我們將介紹MapReduce相關的排序技術。

相關文章/專題:

《Hadoop從入門到精通(上)》連結: http://zt.it168.com/topic/hadoop0104/

《企業大資料平臺MapReduce應用之Join實踐!》連結: http://blog.itpub.net/31545816/viewspace-2218254/

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545816/viewspace-2219003/,如需轉載,請註明出處,否則將追究法律責任。

相關文章