shuffle机制之Partition分区
一、分区概念
分区概念:将数据按照条件处理后,最终输出到多个文件中.
二、源码解析
2.1 通过设置分区数实现分区效果
默认情况下,我们运行WodCount案例可以看到,在输出路径中,所有的数据都存储到一个文件中(part-r-00000)
那么多个分区对应的就是多个不同的文件,比如part-r-00001、part-r-00002…
思考1:为什么设置reduce的个数,就可以实现分区的效果?
首先默认情况下,通过源码MapTaskrun()方法找到runNewMapper(//347行)方法进入~找到output = new NewOutputCollector(//782行)方法进入~查看下面代码并解析
//此位置获取的就是在Driver类中设置的reduce个数,如果没有设置默认就是1个
partitions = jobContext.getNumReduceTasks(); -711行
if (partitions > 1) {
//如果说reduce的个数大于1, 会尝试获取一个分区器类,通过mapreduce.job.partitioner.class参数获取,
// 默认mapreduce.job.partitioner.class没有配置,则直接返回HashPartitioner.class 。
partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>) --partitioner分区器对象
ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
//查看getPartitionerClass()源码~找JobContextImpl(232行)类~查看getClass()方法
//查看【mapred-default.xml】~mapreduce.job.partitioner.class未设置值~使用HashPartitioner
} else { --partitions不大于1,默认是1
// 最终的分区号就是固定的0号分区。
partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() {
@Override
public int getPartition(K key, V value, int numPartitions) {
return partitions - 1; --默认使用的是HashPartitioner分区,返回0,即0号分区
}
};
}
源码解析得出结论:
有多少个分区是由reduce个数决定的,
设置reduce的个数后,源码层面是通过生成一个分区器对象,而分区器对象中的业务处理就是计算最终要分区的个数(通过重写getPartition()方法),所以说,设置了reduce的个数就实现了分区的效果。
2.2 获取到的分区器在哪里使用
通过源码逐步分析:
1、context.write(outk,outv); //--Mapper中的map()方法写出数据到缓冲区,查看源码
2、进入write()方法~TaskInputOutputContext接口~(ctrl+alt+B)查找该接口实现类~进入ChainMapContextImpl类(108行)~output.write()~进入RecordWriter抽象类~查找该抽象类的实现类NewOutputCollector(在MapTask中705行)~找到NewOutputCollector类中的write()方法726行
@Override
public void write(K key, V value) throws IOException, InterruptedException {
//collector可以看作就是缓冲区对象,它去收集kv,并同时通过分区器对象获取的分区号,所以在数据写出到缓冲区之前,分区号就已经是计算出来的
collector.collect(key, value,
partitioner.getPartition(key, value, partitions));
}
2.3 分区的数据具体
1、首先明确一点,数据的分区是由分区器(Partitioner)对象来决定的.
2、Hadoop有默认的分区器对象 HashPartitioner . --通过源码可以看到
HashPartitioner会按照k的hash值对Reduce的个数进行取余操作~得到k所对应的分区.
3、hadoop也支持用户自定义分区器
//HashPartitioner默认分区器具体是如何进行分区号计算的?
//通过ctrl+N查找HashPartitioner类(mapreduce包下)~查看getPartition()方法
public int getPartition(K key, V value,int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
源码分析得出结论:
通过计算每个key的hash值和Int类型的最大值做 与运算,然后对设置的reduce个数进行 取余操作,最终得到分区号。
通过取余操作,余数是0的数据,就进入0号分区文件中,余数是1的,就进入1号分区文件中
与运算的作用是保证得到的数值是正数。
注意:分区的计算不一定是非要按照key进行计算,HADOOP默认情况是通过key计算分区号,也可以自己定义按照value或者key+value等等。
三、自定义分区器
1、自定义分区类需要继承org.apache.hadoop.mapreduce.Partitioner抽象类
2、使用自定义分区器需要在Driver类中通过job.setPartitionerClass(MyPartitioner.class)设置
3、正常情况下,根据分区器业务来决定设置多少个,说白了就是分区器的逻辑会生成多少个分区,则设置的多少个reduce
四、设置分区个数的注意事项
- reduce个数的设置
如果不设置,reduce的个数默认为1 ,则最终的分区号是固定的 0
如果 1 < reduce个数 < 分区数, 报错
如果 reduce个数 > 分区数, 报错, 多出的reduce空跑一趟.
最佳: reduce的个数就设置为实际的分区数.- 分区号只能从0开始,逐一累加