寫在前面
今天繼續學習hadoop部分的知識。
MapReduce
資料切片
InputFormat 介面
InputFormat
是一個抽象類,定義了兩個方法:
getSplits
:負責實現資料切片的方法createRecordReader
:實現資料的 key-value 格式。
FileInputFormat 抽象類
FileInputFormat
是所有以檔案作為資料來源的 InputFormat
實現的基類,儲存作為 job 輸入的所有檔案,並實現了對輸入檔案計算 splits 的方法。不同的子類(如 TextInputFormat
)負責獲得記錄的方法。
TextInputFormat
TextInputFormat
是預設的處理類,用於處理普通文字檔案。檔案中的每一行作為一個記錄:
- Key:每一行的起始偏移量
- Value:每一行的內容
預設分隔符是換行符(\n
)或Enter鍵。
KeyValueTextInputFormat
適用於以製表符(\t
)分割的多列資料:
- Key:分隔符前的部分
- Value:分隔符後的部分
如果沒有分隔符,整行作為 Key,Value 為空。
SequenceFile 輸入格式
適用於二進位制檔案。
CombineFile 輸入格式
適用於小檔案,解決大量小檔案導致的 MapTask 數量過多問題。
InputSplit的大小
InputSplit
的大小由以下公式決定:
Math.max(minSize, Math.min(maxSize, blockSize))
- 單節點建議執行 10—100 個 Map Task。
- Map Task 執行時長不建議低於 1 分鐘,否則效率低。
特殊:一個輸入檔案大小為 140M,應該有幾個 Map Task?
- 由
FileInputFormat
類中的getSplits
方法決定,通常為 1 個 Map Task。
執行流程
Map 任務處理
- 框架使用
InputFormat
的子類把輸入檔案(夾)劃分為多個InputSplit
,預設每個 HDFS 塊對應一個InputSplit
。 RecordReader
將每個InputSplit
解析成<k1, v1>
。- 呼叫
Mapper
類中的map(...)
函式,輸入是<k1, v1>
,輸出是<k2, v2>
。 - 如果有 Reduce,框架會對 Map 輸出的
<k2, v2>
進行分割槽,預設只有 1 個分割槽。 - 資料按
k2
進行排序和分組,相同k2
的v2
會分為一個組。
Reduce 任務處理
- 在 Reduce 階段,框架會將多個 Map 任務的輸出透過網路傳輸到不同的 Reduce 節點,這個過程稱為 Shuffle。
- 框架對接收到的相同分割槽的
<k2, v2>
資料進行合併、排序、分組。 - 呼叫
Reducer
類中的reduce
方法,輸入為<k2, {v2...}>
,輸出為<k3, v3>
。 - 最終結果儲存到 HDFS 中。
自定義輸出類
FileOutputFormat
是預設的輸出類。如果需要自定義輸出格式,需要繼承FileOutputFormat
並在RecordWriter
中根據自定義的輸出邏輯重寫方法。
YARN
資源管理框架
架構
YARN 是 Hadoop 的資源管理框架,採用主從架構:
- ResourceManager:管理叢集資源。
- NodeManager:管理每個節點的資源(如 CPU 和記憶體)。
- ApplicationMaster:任務的排程和資源請求,需要實現
ApplicationMaster
介面才能提交到 YARN 上。
排程策略
- FIFO:先進先出排程策略。
- 公平排程:每個作業按比例分配資源。
- 容量排程:為每個作業分配固定的資源容量。
Shuffle 過程
Shuffle 過程簡介
廣義的 Shuffle 過程是指,在 Map 函式輸出資料之後,並且在 Reduce 函式執行之前的過程。Shuffle 過程包含了資料的分割槽、溢寫、排序、合併等操作。具體而言,Shuffle 主要包括以下步驟:
- 分割槽(Partitioning):根據自定義的分割槽規則,將 Map 輸出的
<k2, v2>
資料分配到不同的 Reduce 任務。 - 溢寫(Spill):當記憶體中儲存的 Map 輸出資料超過閾值時,資料會被溢寫到本地磁碟。
- 排序(Sorting):Map 輸出的資料會按照 Key 值進行排序,預設採用字典順序排序。
- 合併(Merging):多個 Map 任務的輸出會被合併,確保每個分割槽的資料都按順序排列並準備好交給 Reducer 進行處理。
Shuffle 原始碼實現
Shuffle 過程的核心程式碼主要包含在 MapOutputCollector
的子實現類中。該類物件表示緩衝區,負責儲存 Map 輸出資料並處理 Shuffle 階段的操作。
自定義排序
Shuffle 的預設排序方式是按照 Hash 值的字典順序進行排序。如果需要自定義排序,可以透過實現 WritableComparable
介面並重寫 compareTo
方法來指定排序規則。
自定義排序示例:
public class MyCustomComparator implements WritableComparable<MyCustomComparator> {
private int key;
@Override
public int compareTo(MyCustomComparator other) {
// 比較 key 的大小
return Integer.compare(this.key, other.key);
}
// 重寫其他必要的序列化和反序列化方法
}
InputFormat、FileInputFormat、TextInputFormat的區別?
這些類主要用於 Map 階段的資料輸入、切片和格式轉換。
-
InputFormat 介面:
InputFormat
是一個介面,定義了兩個方法:getSplits
:負責實現資料切片。createRecordReader
:將每個切片中的資料轉換為key-value
格式。
-
FileInputFormat:
FileInputFormat
是一個實現了getSplits
方法的抽象基類,負責將輸入資料切片,並處理資料為key-value
格式。它實現了對資料切片的功能,預設按照檔案的 HDFS 塊進行切分,且溢位塊最多為 10%。 -
TextInputFormat:
TextInputFormat
是FileInputFormat
的具體實現類,用於處理文字檔案。它將每一行的偏移量作為key
,每一行的內容作為value
,並預設使用換行符作為行分隔符。它是 MapReduce 的預設輸入格式。
MapReduce 執行流程?
-
Map 階段:
- 資料切片:Map 階段首先將輸入資料切片,每個切片對應一個 Map Task。預設情況下,
TextInputFormat
會將資料按照 HDFS 塊切分。 - 格式化資料:每個切片的資料會透過
RecordReader
轉換為key-value
對,預設情況下,偏移量為key
,每行文字內容為value
。 - Map 輸出:Map Task 處理每個輸入的
key-value
對後,生成新的輸出k2-v2
對。輸出會先進入一個 100MB 大小的環形緩衝區。當緩衝區中的資料達到 80%(80MB)時,資料會被溢寫到磁碟。 - 分割槽和排序:Map 輸出的
k2-v2
對會進行分割槽,分割槽的數量與 Reduce 任務的數量相同。分割槽後,資料會按key
排序(預設是字典順序)。 - Combiner 歸約(可選):如果設定了 Combiner,Map 輸出會在寫入磁碟前進行區域性聚合。
- 資料切片:Map 階段首先將輸入資料切片,每個切片對應一個 Map Task。預設情況下,
-
Shuffle 階段:
- 資料傳輸:Map 輸出的每個分割槽會透過網路傳輸到相應的 Reduce 節點,這個過程叫做 Shuffle。
- 資料合併和排序:在 Reduce 端,接收到的資料會按照
key
進行排序和分組。相同key
的value
會被合併為一個集合。
-
Reduce 階段:
- 資料處理:Reduce Task 接收到
key
和value
集合後,呼叫reduce
方法對資料進行聚合。不同的key
會進入不同的 Reduce Task 中進行處理。 - 結果輸出:Reduce Task 將最終的輸出寫入 HDFS。
- 資料處理:Reduce Task 接收到
如何在 Map 階段進行 Reduce 操作(MapJoin)?
使用 Combiner
可以在 Map 階段進行區域性的聚合,從而減少 Reduce 階段的資料量。Combiner 是一個在 Map 完成後執行的“輕量級” Reduce 操作,它會對每個 Map Task 輸出的資料進行初步的歸約,減少 Shuffle 階段的資料傳輸量。
- MapJoin:在
setup
方法中使用小表資料與大表資料進行連線,避免 Reduce 階段的複雜計算。
MapReduce 最佳化?
資料輸入最佳化
合併小檔案
問題:大量小檔案會導致建立大量 Map Task,從而降低執行效率。
解決方案:
- 使用
CombineTextInputFormat
代替預設的TextInputFormat
,避免為每個小檔案建立單獨的 Map Task。 - 在讀取資料之前,先合併小檔案,減少小檔案導致的 Map Task 數量。
Map 階段最佳化
減少溢寫次數
問題:多次溢寫會導致磁碟 I/O 開銷大,降低效率。
解決方案:
-
增大溢寫觸發的記憶體上限,減少溢寫次數:
- 設定
io.sort.mb
(環形快取區大小)來增大記憶體緩衝區。 - 設定
sort.spill.percent
來調整溢寫觸發的比例(預設為 80%)。
- 設定
-
使用 Combine 進行預聚合,減少 I/O。
-
使用 MapJoin 最佳化資料合併操作,在 Map 階段完成預處理。
I/O 傳輸最佳化
問題:大量資料傳輸會影響 MapReduce 執行效率。
解決方案:
- 採用資料壓縮減少網路 I/O 時間。
- 使用 Combine 或提前過濾資料來減少傳輸的資料量。
- 適當使用本地化 Map 任務,減少跨節點的資料傳輸。
Reduce 階段最佳化
問題: Reduce 階段執行效率慢。
解決方案:
- 合理設定 Reduce 任務的數量,避免過多的 Reduce Task。
- 使用 MapJoin 來減少 Reduce 階段的資料量。
- 使資料分佈均勻,避免資料傾斜。
MapReduce 在 YARN 上的執行流程?
-
資源申請:
ResourceManager
會為每個應用(如 MapReduce 作業)在NodeManager
中申請資源容器(container)。每個容器會分配一定量的記憶體、CPU 和硬碟資源,並啟動一個 JVM 程序。ApplicationMaster
會為作業中的每個 Task(Map 或 Reduce)向ResourceManager
申請容器,並請求啟動該容器。
-
Task 執行:
NodeManager
啟動並分配容器資源後,執行相應的 Map 或 Reduce Task。- Task 執行完後,容器資源會被釋放,
NodeManager
收回容器。
粗粒度與細粒度資源申請
- 細粒度資源申請(MapReduce):每個任務根據需要動態申請資源,例如 1GB 記憶體,只使用所需的資源。
- 粗粒度資源申請(Spark):任務會申請一個固定大小的資源,不管任務大小如何,直到任務完成才釋放資源。Spark 需要更多的記憶體資源,適用於記憶體密集型計算。
YARN 的排程策略?
YARN 中有三種排程策略,分別適用於不同的場景:
-
FIFO Scheduler(先進先出排程器):
按照作業提交的順序進行排程。簡單易用,但可能導致其他作業的資源請求被阻塞,不推薦在生產環境中使用。 -
Capacity Scheduler(容量排程器):
為不同的佇列分配固定的資源容量。適用於多租戶叢集,每個部門或專案都能獲得一定的資源份額。 -
Fair Scheduler(公平排程器):
每個作業根據公平原則動態調整資源分配,適用於多個作業併發執行,保證每個作業公平地分配資源。CDH 中的預設排程策略。
透過合理選擇排程策略和最佳化 MapReduce 作業,可以顯著提高作業的執行效率。