MapReduce框架會確保每一個Reducer的輸入都是按Key進行排序的。一般,將排序以及Map的輸出傳輸到Reduce的過程稱爲混洗(shuffle)。每一個Map都包含一個環形的緩存,默認100M,Map首先將輸出寫到緩存當中。當緩存的內容達到“閾值”時(閾值默認的大小是緩存的80%),一個後臺線程負責將結果寫到硬盤,這個過程稱爲“spill”。Spill過程中,Map仍可以向緩存寫入結果,如果緩存已經寫滿,那麼Map進行等待。
Spill的具體過程如下:首先,後臺線程根據Reducer的個數將輸出結果進行分組,每一個分組對應一個Reducer。其次,對於每一個分組後臺線程對輸出結果的Key進行排序。在排序過程中,如果有Combiner函數,則對排序結果進行Combiner函數進行調用。每一次spill都會在硬盤產生一個spill文件。因此,一個Map task有可能會產生多個spill文件,當Map寫出最後一個輸出時,會將所有的spill文件進行合併與排序,輸出最終的結果文件。在這個過程中Combiner函數仍然會被調用。從整個過程來看,Combiner函數的調用次數是不確定的。
Hadoop是如何進行排序的呢?根據筆者的理解,MapReduce的排序過程分爲兩個步驟,一個按照Key進行排序;一個是按照Key進行分組。這兩部分分別由SortComparator和GroupingComparator來完成。具體的配置如下面黑體所示:
job.setPartitionerClass(FirstPartitioner.class);
job.setSortComparatorClass(KeyComparator.class);
job.setGroupingComparatorClass(GroupComparator.class);
如果用戶想自定義排序方式,首先需要實現兩個Comparator並將其按照上面的格式進行配置。每一個Comparator需要繼承WritableComparator基類。如下所示:
public static class GroupComparator extends WritableComparator {
protected GroupComparator() {
super(IntPair.class, true);
}
@Override
public int compare(WritableComparable w1, WritableComparable w2) {
IntPair ip1 = (IntPair) w1;
IntPair ip2 = (IntPair) w2;
return IntPair.compare(ip1.getFirst(), ip2.getFirst());
}
}