如何自定义分区:
原理:int getPartition(LongWritable key, Text value, int numPartitions)
此方法的返回值为分区的索引(分区就是由索引作为唯一标识符),
该方法确定当前key/value属于哪个索引对应的分区。
由于 if (partition < 0 || partition >= partitions) {
throw new IOException("Illegal partition for " + key + " (" + partition + ")");
}
上述代码,可见,自定义分区索引时的范围为:
0 ~ partitions==job.getNumReduceTasks()-1;
shuffle(混洗):
说明:shuffle技术是MR的核心技术
shuffle阶段:从map输出开始,到reduce输入之前这个阶段。
流程:
map输出-->
-->缓存区(buffer,默认100M)
-->partition(在buffer标记)
-->sort(将kv对的元数据进行排序,依然是在buffer内进行)
-->spill(条件为buffer中的数据达到80%,即0.8时,开始溢写)到本地磁盘,
产生溢写文件,
将同一分区的数据聚集(此时数据是排序状态)
-->n个溢写文件merge成一个文件
-->fetch(reduce端的线程通过Http协议复制当前reduceTask要处理的分区数据,
先复制到缓存,
再根据缓存大小来决定是否产生文件)
-->merge(reduce端会合并多个文件,
最后一次合并不产生文件,直接在内存中输入reduce)
环形缓冲区:【MapOutputBuffer】的源码解析
属性:
private int partitions;
private Class<!-- <K> --> keyClass; //在job中定义的map输出端的key类型
private Class<!-- <V> --> valClass; //在job中定义的map输出端的value类型
private Serializer<!-- <K> --> keySerializer; //用来序列号key值,存储到buffer里
private Serializer<!-- <V> --> valSerializer; //用来序列化value值,存储到buffer里
// k/v accounting
//参考init方法内的赋值操作,实际上是对buffer进行包装成int类型的数组,提供存储元数据的界面
private IntBuffer kvmeta;
//用来标记元数据的起始位置 (第一次的位置应该是倒数第四个int元素位置)
int kvstart;
int kvend; // 元数据在int数组的结束位置
int kvindex; // 存储下一个元数据的起始位置
int equator; // 分隔点的位置
int bufstart; // kv对的起始位置
int bufend; // kv对的结束位置
int bufmark; // kv对的结束位置的标记
int bufindex; // 下一个kv对的开始位置
int bufvoid; // 缓冲区的长度
byte[] kvb