目录

MapReduce中的分组

1 默认分组

2 自定义分组

3 可能存在的问题

4 总结


MapReduce中的分组

分组组件是Map端至Reduce端过程中的一部分,即:Map -----> 分组组件 ------> Reduce;

1 默认分组

默认情况下:

Reduce端接收到的数据是按照map输出的key进行分组;分组时,Key相同的为一组;Key中的对象,均实现了WritableComparable接口。

当没有自定义分组时,即:默认分组;就会自动去找接口中的compareTo()方法。该方法返回值是0的,分为为一组;返回值不为的0,则分到下一组;

比如:在WordCount程序中,map输出的Key是单词,我们就是想让相同的单词为一组,即分组字段就是map输出的Key。

但如果我想要的分组,并不是Key中的全部字段,这该怎么办呢?

比如:我现在想统计各年级学生的成绩,而年级字段是:3年3班,3年2班等。若还按照年级为key发送,则会是3年3班的数据在一起,3年2班的数据在一起。而不是按年级划分、分组的。

若只想要年级字段,则需要自定义分组。

再比如,课程分数统计面试题中,有这样的一个需求:求出每门课程中、学生成绩平均分最高学生的信息。

解题思路:

这道题的实质是:分组求最大值,只要处理好两件事情即可实现。一是分组,二是排序(排序要巧妙的运用shuffle过程)。

若想在Shuffle过程中进行排序,则需要让排序字段为key;

①若map端发出的key是分数;会造成相同的分数调用一次reduce方法,无法保证相同分数是同一门课程。越来越偏离需求。

②若map端发出的key是科目;则需要在reduce端求最大值,这容易产生数据倾斜,所以这不是最佳方案。

最佳方案为:map发出的key是:科目和分数;即:按分数排序,按科目分组。

若以科目和分数同时作为key,默认情况下必然会造成,相同科目和相同分数的数据汇总到一组中。

所以应该自定义分组!!!

2 自定义分组

以自定义分组的方式解决上面的分数统计问题。设计MapReduce,其中:

map:拆分课程和分数字段,并封装成对象(用于自定义排序)作为key进行发送。value:姓名。

reduce:发送到Reduce端的数据已经是分好组、排好序的了,所以reduce依旧是统计工作。

分组说白了就是比较的过程,按照key比较,只要返回值是0的就会认为是一组;不返回0,就化分到下一组中。

自定义分组流程:

①创建自定义分组类,并继承WritableComparator类;

②重写compare()方法;

public class MyGroup extends WritableComparator{
	public MyGroup() {
// 默认情况下,第二个参数是false,不会构建自定义的实例对象,需要手动修改为true
		super(ScoreBean.class,true);
	}

public int compare(WritableComparable a, WritableComparable b) {
		ScoreBean ascBean = (ScoreBean)a;
		ScoreBean bscBean = (ScoreBean)b;
		// 只关心返回0的值
		return ascBean.getCourse().compareTo(bscBean.getCourse()) ;
 }
}

③在驱动类中添加自定义分组;

job.setGroupingComparatorClass(MyGroup.class)

3 可能存在的问题

课程分数统计最终想要的结果是:按科目分好组之后,在组内对分数进行排序。但按照之前的写法会输出类似以下的结果:

mapreduce 自定义分组 自定义分区 mapreduce分组详解_字段

从这个结果可以看出,自定义分组有作用,但没有完全成功。分组位置不对,产生了先排序,后分组的效果。这是怎么回事呢?

实际上,在map到reduce过程中,默认的是先排序,后分组。所以在重写排序函数compareTo()时,需将分组字段纳入排序范围,这样就会将相同的字段值排在一起。比如:先按课程排序,再按分数排序。

需要将原来排序的代码进行修改,如下所示:

原排序代码:

/**
     * 定义排序规则
     * 按照分数降序排列
     * 产生先排序,后分组的效果
	 */
	@Override
	public int compareTo(ScoreBean o) {
		return o.getScore()-this.getScore()>0?1:(o.getScore()-this.getScore()<0?-1:0);
	}

修改后的代码:

/**
	 * 定义排序规则
	 * 按照分数降序排列
	 */
	@Override
	public int compareTo(ScoreBean o) {
		// 先进行课程比较,目的是到reduce端,相同的课程在排一起
		int tmp = o.getCourse().compareTo(this.getCourse());
		if(tmp == 0) {
			// 课程相同,排分数
			return o.getScore()-this.getScore()>0?1:(o.getScore()-this.getScore()<0?-1:0);
		}
		return tmp;
	}

4 总结

(1)当既有分组字段又有排序字段时,分组字段一定是排序字段的前几个。

因为MapReduce是排序在前,分组在后,所以,排序字段中一定要包含分组字段,且分组字段的排序要写在排序字段的前面。

比如:排序:A      B    C; 分组:D     F

实际上,排序方法中的字段顺序应该是:D   F    A    B    C;分组字段是:D  F

这是为了保证相同的分组数据相邻。

实际上的分组,仅仅是将map输出的结果,相邻的数据进行比较。即仅仅会比较前一条数据和后一条数据。如果返回相同的值,则为一组;如果不是,重新划分组。所以,若想分组,则一定要能够保证分组字段的数据在相邻的位置。

(2)Reduce中的Iterable<Text> values 只能循环遍历一次;每次循环遍历完指针都会移动到最后一个。

(3)Reduce中的迭代器中所有对象共用同一个地址;