MapReduce 是 Hadoop 計算引擎的核心編程模型。理解其內部流程——數據如何切分、Map 輸出如何排序溢寫、Shuffle 如何歸併、Reduce 如何分組輸出——對性能調優、故障排查、自定義組件開發至關重要。本文基於 Hadoop 2.x (YARN) 源碼,按數據流向串聯講解全鏈路關鍵類與機制。

優勢:RM 僅管 Job 級資源,Task 級調度下放給 AM,支持多框架混部(Spark、Flink 等)。
// 用戶配置 job.setInputFormatClass(TextInputFormat.class); // 核心接口 public abstract class InputFormat{ List getSplits(JobContext ctx); // 邏輯切分 RecordReader createRecordReader(InputSplit split, TaskAttemptContext ctx); // 物理讀取 }
length、locations(數據所在節點)。Split 數 = MapTask 數。<K, V> 記錄流。TextInputFormat 默認 LineRecordReader,按 \n 切行,Key=偏移量,Value=行文本。YarnChild → MapTask.run()MapTask.runNewMapper() 反射實例化 Mapper、InputFormat、RecordReaderMapContextImpl 注入 InputSplit、RecordReader、OutputCollectorcontext.nextKeyValue() → mapper.map()Map 輸出不直接寫磁盤,先進入環形內存緩衝區(默認 100MB,mapreduce.task.io.sort.mb)。
kvbuffer:byte[] 存序列化後的 Key、Value、Partitionkvmeta:int[] 索引元數據(每條 16 字節:partition、key offset、key length、value length)soft limit:緩衝區使用達 80%(mapreduce.map.sort.spill.percent)觸發溢寫SpillThread,不阻塞 Map 輸出kvmeta 按 (partition, key) 快速排序Combiner,對同 Key 值做局部聚合spillN.out,並生成索引 spillN.out.indexMap 結束前,將所有溢寫文件合併為單一 mapoutput/file.out(若溢寫文件數 ≥ mapreduce.task.io.sort.factor 默認 10,採用多輪歸併)。最終輸出有序、分區、可選 Combiner 聚合後的文件。
Fetcher 線程(默認 5 個)HTTP 拉取 Map 輸出。根據數據大小選擇 InMemoryMapOutput 或 OnDiskMapOutput。MergeManager,內存/磁盤歸併排序,產生 RawKeyValueIterator。reduce()。public interface Partitioner{ int getPartition(K key, V value, int numPartitions); // numPartitions = reduce task 數 }
HashPartitioner:key.hashCode() & 0x7FFFFFFF % numPartitions(partition, key) 全局有序GroupingComparator 僅比較 Key,相鄰 Key 相等則歸為同一組,一次 reduce() 處理WritableComparator 按字節比較;自定義可實現「只比 ID 不比時間」等業務分組public void run(Context context) {
setup(context);
while (context.nextKey()) { // 移動到下一組
reduce(context.getCurrentKey(), // 當前 Key
context.getValues(), // 同組 Value 迭代器
context);
// 重置備用存儲(如有)
}
cleanup(context);
}
getValues() 返回 Iterable,底層封裝 RawKeyValueIterator,按需讀取,支持超大 Value 流式處理。checkOutputSpecs():驗證輸出目錄不存在(防誤覆蓋),拋 FileAlreadyExistsExceptiongetRecordWriter():返回 RecordWriter 寫數據getOutputCommitter():返回 OutputCommitter 管理兩階段提交| 目錄 | 用途 |
|---|---|
${output}/_temporary/${attemptId} | Task 臨時輸出目錄 |
${output}/_temporary | Job 級臨時目錄(所有成功 Task 移至此) |
${output} | 最終輸出目錄 |
_temporary/${attemptId}commitTask() 移至 _temporarycommitJob() 重命名 _temporary → ${output},生成 _SUCCESS 標記abortTask() 刪除臨時目錄,重試時乾淨無殘留TextOutputFormat 默認 LineRecordWriter,Key/Value 以 \t 分隔,換行結束mapreduce.output.fileoutputformat.compress=true + CompressionCodec)part-r-00000,FileOutputFormat.getUniqueFile() 保證唯一性(含 Task 類型 m/r、分區號)| 症狀 | 排查方向 | 典型參數/手段 |
|---|---|---|
| Map 端溢寫頻繁 | 緩衝區過小 | 增大 mapreduce.task.io.sort.mb、調低 spill.percent |
| Reduce 端 Data Skew | Partition 分布不均 | 自定義 Partitioner、Key 加鹽、Combine 輸入預聚合 |
| Shuffle 讀取超時 | 網絡/磁盤慢 | 增大 mapreduce.reduce.shuffle.parallelcopies、啟用壓縮 mapreduce.map.output.compress |
| 小文件過多 | Map 數過大/Reduce 數過小 | 調整 mapreduce.input.fileinputformat.split.maxsize、合併小文件 |
| Combiner 無效 | 邏輯不滿足結合律 | 僅用於可交換/結合操作(求和、計數、最大值),去重、平均值不可用 |
MapReduce 全鏈路核心在於 分治 + 排序 + 兩階段提交:
掌握這些機制,不僅能寫出正確的 MapReduce 程序,更能在面對數據傾斜、OOM、小文件、任務卡死等生產疑難雜症時,從源碼層面精準定位、精準施策。