在MapReduce的Map阶段中,Partitioner(分区器)的作用发生在map函数输出键值对之后,但在数据被写入磁盘(spill到本地文件)之前。具体流程如下:
分区发生的具体阶段:
Map函数处理完成
当map函数处理完输入数据并调用context.write(key, value)
输出键值对时,每个键值对会进入一个内存缓冲区(in-memory buffer)。立即分区
在键值对被写入内存缓冲区的过程中,Partitioner的getPartition()
方法会被调用,根据键(Key)和配置的分区规则(如HashPartitioner的默认哈希分配),立即确定该键值对属于哪个Reduce分区。
关键点:分区逻辑是在内存中实时计算的,而非等到所有map任务结束后才执行。内存缓冲区与Spill到磁盘
- 内存缓冲区默认大小为100MB(可配置),当达到阈值(如80%)时,会触发spill到本地磁盘的操作。
- 在spill之前,缓冲区内的数据会按分区号排序,同一分区的数据聚集在一起,并为每个分区生成一个有序的临时文件。
合并与最终输出
所有spill文件最终会被合并为一个按分区排序且分区内有序的输出文件(map output
),等待ReduceTask拉取。
为什么分区发生在map输出阶段?
- 目的:确保数据在写入磁盘时已经按分区划分,避免后续ReduceTask处理全量数据。
- 性能优化:分区与map输出同时进行,减少了后续排序和传输的开销。
关键代码逻辑(简化版):
// MapTask内部逻辑(简化)
while (input.hasMore()) {
keyVal = input.read();
mappedKeyVal = mapFunction.process(keyVal); // 用户map函数
partition = partitioner.getPartition(mappedKeyVal.key, mappedKeyVal.value, numPartitions);
buffer.addToPartition(partition, mappedKeyVal); // 按分区写入缓冲区
if (buffer.full()) {
sortAndSpill(); // 按分区排序并写入磁盘
}
}
总结
Partitioner在Map阶段的作用时机是:map函数每输出一个键值对后,立即计算其分区号,并在内存中按分区缓存数据。这一设计保证了MapReduce的高效性和扩展性。