hadoop基礎知識分享(二)

ikestu小猪發表於2024-11-06

寫在前面

今天繼續學習hadoop部分的知識。

MapReduce

資料切片

InputFormat 介面

InputFormat 是一個抽象類,定義了兩個方法:

  1. getSplits:負責實現資料切片的方法
  2. 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 任務處理

  1. 框架使用 InputFormat 的子類把輸入檔案(夾)劃分為多個 InputSplit,預設每個 HDFS 塊對應一個 InputSplit
  2. RecordReader 將每個 InputSplit 解析成 <k1, v1>
  3. 呼叫 Mapper 類中的 map(...) 函式,輸入是 <k1, v1>,輸出是 <k2, v2>
  4. 如果有 Reduce,框架會對 Map 輸出的 <k2, v2> 進行分割槽,預設只有 1 個分割槽。
  5. 資料按 k2 進行排序和分組,相同 k2v2 會分為一個組。

Reduce 任務處理

  1. 在 Reduce 階段,框架會將多個 Map 任務的輸出透過網路傳輸到不同的 Reduce 節點,這個過程稱為 Shuffle
  2. 框架對接收到的相同分割槽的 <k2, v2> 資料進行合併、排序、分組。
  3. 呼叫 Reducer 類中的 reduce 方法,輸入為 <k2, {v2...}>,輸出為 <k3, v3>
  4. 最終結果儲存到 HDFS 中。

自定義輸出類

  • FileOutputFormat 是預設的輸出類。如果需要自定義輸出格式,需要繼承 FileOutputFormat 並在 RecordWriter 中根據自定義的輸出邏輯重寫方法。

YARN

資源管理框架

架構

YARN 是 Hadoop 的資源管理框架,採用主從架構:

  • ResourceManager:管理叢集資源。
  • NodeManager:管理每個節點的資源(如 CPU 和記憶體)。
  • ApplicationMaster:任務的排程和資源請求,需要實現 ApplicationMaster 介面才能提交到 YARN 上。

排程策略

  1. FIFO:先進先出排程策略。
  2. 公平排程:每個作業按比例分配資源。
  3. 容量排程:為每個作業分配固定的資源容量。

Shuffle 過程

Shuffle 過程簡介

廣義的 Shuffle 過程是指,在 Map 函式輸出資料之後,並且在 Reduce 函式執行之前的過程。Shuffle 過程包含了資料的分割槽、溢寫、排序、合併等操作。具體而言,Shuffle 主要包括以下步驟:

  1. 分割槽(Partitioning):根據自定義的分割槽規則,將 Map 輸出的 <k2, v2> 資料分配到不同的 Reduce 任務。
  2. 溢寫(Spill):當記憶體中儲存的 Map 輸出資料超過閾值時,資料會被溢寫到本地磁碟。
  3. 排序(Sorting):Map 輸出的資料會按照 Key 值進行排序,預設採用字典順序排序。
  4. 合併(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 是一個介面,定義了兩個方法:

    1. getSplits:負責實現資料切片。
    2. createRecordReader:將每個切片中的資料轉換為 key-value 格式。
  • FileInputFormat
    FileInputFormat 是一個實現了 getSplits 方法的抽象基類,負責將輸入資料切片,並處理資料為 key-value 格式。它實現了對資料切片的功能,預設按照檔案的 HDFS 塊進行切分,且溢位塊最多為 10%。

  • TextInputFormat
    TextInputFormatFileInputFormat 的具體實現類,用於處理文字檔案。它將每一行的偏移量作為 key,每一行的內容作為 value,並預設使用換行符作為行分隔符。它是 MapReduce 的預設輸入格式。

MapReduce 執行流程?

  1. 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 輸出會在寫入磁碟前進行區域性聚合。
  2. Shuffle 階段

    • 資料傳輸:Map 輸出的每個分割槽會透過網路傳輸到相應的 Reduce 節點,這個過程叫做 Shuffle。
    • 資料合併和排序:在 Reduce 端,接收到的資料會按照 key 進行排序和分組。相同 keyvalue 會被合併為一個集合。
  3. Reduce 階段

    • 資料處理:Reduce Task 接收到 keyvalue 集合後,呼叫 reduce 方法對資料進行聚合。不同的 key 會進入不同的 Reduce Task 中進行處理。
    • 結果輸出:Reduce Task 將最終的輸出寫入 HDFS。

如何在 Map 階段進行 Reduce 操作(MapJoin)?

使用 Combiner 可以在 Map 階段進行區域性的聚合,從而減少 Reduce 階段的資料量。Combiner 是一個在 Map 完成後執行的“輕量級” Reduce 操作,它會對每個 Map Task 輸出的資料進行初步的歸約,減少 Shuffle 階段的資料傳輸量。

  • MapJoin:在 setup 方法中使用小表資料與大表資料進行連線,避免 Reduce 階段的複雜計算。

MapReduce 最佳化?

資料輸入最佳化

合併小檔案

問題:大量小檔案會導致建立大量 Map Task,從而降低執行效率。
解決方案:

  1. 使用 CombineTextInputFormat 代替預設的 TextInputFormat,避免為每個小檔案建立單獨的 Map Task。
  2. 在讀取資料之前,先合併小檔案,減少小檔案導致的 Map Task 數量。

Map 階段最佳化

減少溢寫次數

問題:多次溢寫會導致磁碟 I/O 開銷大,降低效率。
解決方案:

  1. 增大溢寫觸發的記憶體上限,減少溢寫次數:

    • 設定 io.sort.mb(環形快取區大小)來增大記憶體緩衝區。
    • 設定 sort.spill.percent 來調整溢寫觸發的比例(預設為 80%)。
  2. 使用 Combine 進行預聚合,減少 I/O。

  3. 使用 MapJoin 最佳化資料合併操作,在 Map 階段完成預處理。

I/O 傳輸最佳化

問題:大量資料傳輸會影響 MapReduce 執行效率。
解決方案:

  1. 採用資料壓縮減少網路 I/O 時間。
  2. 使用 Combine 或提前過濾資料來減少傳輸的資料量。
  3. 適當使用本地化 Map 任務,減少跨節點的資料傳輸。

Reduce 階段最佳化

問題: Reduce 階段執行效率慢。
解決方案:

  1. 合理設定 Reduce 任務的數量,避免過多的 Reduce Task。
  2. 使用 MapJoin 來減少 Reduce 階段的資料量。
  3. 使資料分佈均勻,避免資料傾斜。

MapReduce 在 YARN 上的執行流程?

  1. 資源申請

    • ResourceManager 會為每個應用(如 MapReduce 作業)在 NodeManager 中申請資源容器(container)。每個容器會分配一定量的記憶體、CPU 和硬碟資源,並啟動一個 JVM 程序。
    • ApplicationMaster 會為作業中的每個 Task(Map 或 Reduce)向 ResourceManager 申請容器,並請求啟動該容器。
  2. Task 執行

    • NodeManager 啟動並分配容器資源後,執行相應的 Map 或 Reduce Task。
    • Task 執行完後,容器資源會被釋放,NodeManager 收回容器。

粗粒度與細粒度資源申請

  • 細粒度資源申請(MapReduce):每個任務根據需要動態申請資源,例如 1GB 記憶體,只使用所需的資源。
  • 粗粒度資源申請(Spark):任務會申請一個固定大小的資源,不管任務大小如何,直到任務完成才釋放資源。Spark 需要更多的記憶體資源,適用於記憶體密集型計算。

YARN 的排程策略?

YARN 中有三種排程策略,分別適用於不同的場景:

  1. FIFO Scheduler(先進先出排程器)
    按照作業提交的順序進行排程。簡單易用,但可能導致其他作業的資源請求被阻塞,不推薦在生產環境中使用。

  2. Capacity Scheduler(容量排程器)
    為不同的佇列分配固定的資源容量。適用於多租戶叢集,每個部門或專案都能獲得一定的資源份額。

  3. Fair Scheduler(公平排程器)
    每個作業根據公平原則動態調整資源分配,適用於多個作業併發執行,保證每個作業公平地分配資源。CDH 中的預設排程策略。

透過合理選擇排程策略和最佳化 MapReduce 作業,可以顯著提高作業的執行效率。

相關文章