一直对MapReduce的分区和分组理解的比较模糊和不确定。这次又遇到reduce输出结果跟自己预想的不一样的情况,因此决定深入进去操作一下各种情况的结果,争取理清楚分组、分区的关系。
我一直以来对MapReduce的分区和分组有一个自己的理解。
分区:如果不自定义分区类,而使用默认分区时,采取的是对键进行哈希操作,并与reducetask任务数取模,根据得到的值进行分区。由于默认的reducetask任务数设置为1,因此默认情况下只有1个分区。如果自己重新定义了一个分区类,则会按照自定义的方式进行分区。
分组:分组和分区类似,也是用来划分数据集的,只不过更加细粒度。如果不自定义分组类而使用默认分组的话,跟默认分区相同,也是通过比较键来进行分组。reduce()函数是按照组为操作对象进行统计的。
摩根士丹利MORGANSTANLEY,美国
工商银行INDUSTRIALCOMMERBANKOFCHINA,中国
TalanxTALANX,德国
华为投资控股HUAWEIINVESTMENTHOLDING,中国
天津物产TEWOOGROUP,中国
阿里巴巴ALIBABAGROUPHOLDING,中国
FomentoEconmicoMexicanoFOMENTOECONMICOMEXICANO,墨西哥
农业银行AGRICULTURALBANKOFCHINA,中国
戴尔科技DELLTECHNOLOGIES,美国
太平洋保险股份CHINAPACIFICINSURANCE,中国
浙江吉利控股ZHEJIANGGEELYHOLDINGGROUP,中国
建设银行CHINACONSTRUCTIONBANK,中国
电子信息产业CHINAELECTRONICS,中国
江苏沙钢JIANGSUSHAGANGGROUP,中国
上海汽车股份SAICMOTOR,中国
厦门建发XIAMENCD,中国
电信CHINATELECOMMUNICATIONS,中国
甲骨文ORACLE,美国
广州汽车工业GUANGZHOUAUTOMOBILEINDUSTRYGROUP,中国
山东能源SHANDONGENERGYGROUP,中国
联想LENOVOGROUP,中国
正威国际AMERINTERNATIONALGROUP,中国
移动通信CHINAMOBILECOMMUNICATIONS,中国
陕西延长石油责任SHAANXIYANCHANGPETROLEUM,中国
航天科工CHINAAEROSPACESCIENCEINDUSTRY,中国
富士通FUJITSU,日本
思科CISCOSYSTEMS,美国
微软MICROSOFT,美国
北京汽车BEIJINGAUTOMOTIVEGROUP,中国
public class HandleTopAttend {
public static class CmpnyCutry implements WritableComparable {
private String company;
private String country;
public void set(String first, String second) {
this.company = first;
this.country = second;
}
public String getCompany() {
return this.company;
}
public String getCountry() {
return this.country;
}
@Override
public void readFields(DataInput in) throws IOException {
// TODO Auto-generated method stub
company = in.readUTF();
country = in.readUTF();
}
@Override
public void write(DataOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeUTF(company);
out.writeUTF(country);
}
@Override
public int compareTo(CmpnyCutry o) {// 先按照公司比较,再按照所属国家比较
// TODO Auto-generated method stub
int i = 0;
if (o instanceof CmpnyCutry) {
CmpnyCutry cc = (CmpnyCutry) o;
i = this.company.compareTo(cc.company);
if (i == 0) {
return this.country.compareTo(cc.country);
}
}
return i;
}
}
public static class Mapper1 extends Mapper {
static int count = 0;
static Map m;
CmpnyCutry cmcu = new CmpnyCutry();
public void setup(Context context) {
m = new HashMap<>();
}
public void map(LongWritable index, Text line, Context context) throws IOException, InterruptedException {
String[] cc = line.toString().split(",");
m.put(cc[0], cc[1]);
count++;//有多少行数据,最后count就为几,代表500强的公司数
}
public void cleanup(Context context) throws IOException, InterruptedException {
Set s = m.entrySet();
Iterator> it = s.iterator();
while (it.hasNext()) {
Entry en = it.next();
cmcu.set(en.getKey(), en.getValue());
context.write(cmcu, new IntWritable(count));//map输出的key:(公司名,国家);value:500强公司数
}
}
}
public static class CountryPartitioner extends Partitioner {
public int getPartition(CmpnyCutry key, IntWritable count, int numPartitions) {
if(key.getCountry().equals("中国")){
return 0;
}else{
return 1;
}
}
}
public static class Reducer1 extends Reducer {
int groupCount = 0;//统计当前分区中有多少个分组
public void reduce(CmpnyCutry cc, Iterable count, Context context)
throws IOException, InterruptedException {
int sum = 0;//统计当前分组的集合中有多少个元素
IntWritable iw = new IntWritable();
Iterator i = count.iterator();
while (i.hasNext()) {
iw = i.next();
sum++;
}
//输出:国家,公司数,第几组,500强公司数
context.write(new Text(cc.getCountry() + "," + sum+","+groupCount++), iw);
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
conf.set("mapred.textoutputformat.ignoreseparator","true");
conf.set("mapred.textoutputformat.separator",",");
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount ");
System.exit(2);
}
Job job = Job.getInstance(conf, "topAttend");
job.setJarByClass(HandleTopAttend.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
job.setMapperClass(Mapper1.class);
job.setMapOutputKeyClass(CmpnyCutry.class);
job.setMapOutputValueClass(IntWritable.class);
job.setPartitionerClass(CountryPartitioner.class);
job.setNumReduceTasks(2);
job.setReducerClass(Reducer1.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
查看输出日志
因为只有一个输入文件,且文件很小不足一个splits(分片),因此map tasks=1。同时由于设置了两个分区和两个reducetask,因此reduce tasks=2。关键是看reduce input groups的数量,可以看到groups数量为29,也就是原始数据集的记录数。因为自定义了组合键,每条记录的组合键都不相同,因此这个结果证明了分组是按照键进行的。
下面是输出的part-r-00000文件内容
这个结果,证明了reducer类和其中的reduce()函数各自的处理范围。reducer类处理整个分区的数据,其操作对象是区,一个区调用一次reducer类。而reduce()函数的操作对象是组,也就是分区中有 几个分组就调用几次reduce()函数,reduce()函数对分组对应的集合进行处理。结合输出的第二个分区文件part-r-00001可以进一步佐证。
下面是输出的part-r-00001文件内容
现在想要实现的是,将相同国家的数据分到一个组中进行整合,也就是按照“国家”进行分组。因此要自定义分组类。
public class HandleTopAttend {
public static class CmpnyCutry implements WritableComparable {
private String company;
private String country;
public void set(String first, String second) {
this.company = first;
this.country = second;
}
public String getCompany() {
return this.company;
}
public String getCountry() {
return this.country;
}
@Override
public void readFields(DataInput in) throws IOException {
// TODO Auto-generated method stub
company = in.readUTF();
country = in.readUTF();
}
@Override
public void write(DataOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeUTF(company);
out.writeUTF(country);
}
@Override
public int compareTo(CmpnyCutry o) {// 先按照公司比较,再按照所属国家比较
// TODO Auto-generated method stub
int i = 0;
if (o instanceof CmpnyCutry) {
CmpnyCutry cc = (CmpnyCutry) o;
i = this.company.compareTo(cc.company);
if (i == 0) {
return this.country.compareTo(cc.country);
}
}
return i;
}
}
public static class Mapper1 extends Mapper {
static int count = 0;
static Map m;
CmpnyCutry cmcu = new CmpnyCutry();
public void setup(Context context) {
m = new HashMap<>();
}
public void map(LongWritable index, Text line, Context context) throws IOException, InterruptedException {
String[] cc = line.toString().split(",");
m.put(cc[0], cc[1]);
count++;//有多少行数据,最后count就为几,代表500强的公司数
}
public void cleanup(Context context) throws IOException, InterruptedException {
Set s = m.entrySet();
Iterator> it = s.iterator();
while (it.hasNext()) {
Entry en = it.next();
cmcu.set(en.getKey(), en.getValue());
context.write(cmcu, new IntWritable(count));//map输出的key:(公司名,国家);value:500强公司数
}
}
}
public static class CountryPartitioner extends Partitioner {
public int getPartition(CmpnyCutry key, IntWritable count, int numPartitions) {
if(key.getCountry().equals("中国")){
return 0;
}else{
return 1;
}
}
}
//自定义分组类,重写compare()方法
public static class GroupingComparator extends WritableComparator {
protected GroupingComparator() {
super(CmpnyCutry.class, true);
}
@SuppressWarnings("rawtypes")
public int compare(WritableComparable cc1, WritableComparable cc2) {
CmpnyCutry ip1 = (CmpnyCutry) cc1;
CmpnyCutry ip2 = (CmpnyCutry) cc2;
String l = ip1.getCountry();
String r = ip2.getCountry();
return l.compareTo(r);// 比较两个字符串的大小
}
}
public static class Reducer1 extends Reducer {
int groupCount = 0;//统计当前分区中有多少个分组
public void reduce(CmpnyCutry cc, Iterable count, Context context)
throws IOException, InterruptedException {
int sum = 0;//统计当前分组的集合中有多少个元素
IntWritable iw = new IntWritable();
Iterator i = count.iterator();
while (i.hasNext()) {
iw = i.next();
sum++;
}
//输出:国家,公司数,第几组,500强公司数
context.write(new Text(cc.getCountry() + "," + sum+","+groupCount++), iw);
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
conf.set("mapred.textoutputformat.ignoreseparator","true");
conf.set("mapred.textoutputformat.separator",",");
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount ");
System.exit(2);
}
Job job = Job.getInstance(conf, "topAttend");
job.setJarByClass(HandleTopAttend.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
job.setMapperClass(Mapper1.class);
job.setMapOutputKeyClass(CmpnyCutry.class);
job.setMapOutputValueClass(IntWritable.class);
job.setPartitionerClass(CountryPartitioner.class);
job.setGroupingComparatorClass(GroupingComparator.class);//为job配置自定义的分组类
job.setNumReduceTasks(2);
job.setReducerClass(Reducer1.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
查看输出的日志结果
可以看到groups数由之前的29变为了5,说明自定义分组其作用了。进一步分析,自定义分组规则是按照国家进行分组的。此处显示5,说明数据集中总共应该有5个国家,查看上面测试一输出的两个文件,发现果然总共有5个国家。进一步分析,第一个分区是“中国”,并且因为在这里设置了按国家分组,因此输出的part-r-00000文件应该只有一行记录,而part-r-00001应该有4条记录。
查看输出文件part-r-00000
查看输出文件part-r-00001
进一步分析每条记录的信息,可以更加佐证测试一的结论。也就是,关于reducer类和reduce()函数的作用范围的结论。
很高兴的是,实际结果证明自己以前的认识基本是正确的!!!!!
reducer类的操作对象是分区,一个分区初始化一次reducer类。reduce()函数的操作对象是组,一个分区中有几个分组就调用几次reduce()函数。
分组默认采用通过比较键的方式来实现。当自定义组合键时,往往需要根据组合键中的某一个属性进行分组统计,此时就用到自定义分组类。通过重写其中的compare()方法定义分组规则。
由于输出结果中没有将整个组合键输出,因此没有体现出排序的过程。实际上,排序应该是在map端执行溢写的时候进行的操作,操作的代码就是自定义组合键中的compareTo()方法。到达溢写条件时,先锁定要溢写的数据,然后对其进行分区,然后在分区内进行排序。如果设置了combiner类,还会在排序后执行combine操作。最后才将结果写出到临时文件中。
不知道有没有不对的地方,因为都是自己的认识,不是正规军。希望各位大佬指正!互相学习!
写博客不容易,转载请注明出处,谢谢!!!https://blog.csdn.net/ASN_forever/article/details/81778972
真的是刚做完上面的实验,就又发现问题了。。。。。
虽然上面通过自定义分组类之后,貌似数据集的输出结果确实是实现分组了。但当数据集换成下面这个时,情况就又变啦。。。
华为投资控股HUAWEIINVESTMENTHOLDING,中国
天津物产TEWOOGROUP,中国
日本瑞穗金融MIZUHOFINANCIALGROUP,日本
阿里巴巴ALIBABAGROUPHOLDING,中国
英国葛兰素史克GLAXOSMITHKLINE,英国
戴尔科技DELLTECHNOLOGIES,美国
腾讯控股TENCENTHOLDINGS,中国
浙江吉利控股ZHEJIANGGEELYHOLDINGGROUP,中国
通用汽车GENERALMOTORS,美国
国家电网STATEGRID,中国
电子信息产业CHINAELECTRONICS,中国
美国银行BANKOFAMERICACORP,美国
江苏沙钢JIANGSUSHAGANGGROUP,中国
上海汽车股份SAICMOTOR,中国
厦门建发XIAMENCD,中国
电信CHINATELECOMMUNICATIONS,中国
OrangeORANGE,法国
广州汽车工业GUANGZHOUAUTOMOBILEINDUSTRYGROUP,中国
联合信贷UNICREDITGROUP,意大利
山东能源SHANDONGENERGYGROUP,中国
兴业银行INDUSTRIALBANK,中国
SAPSAP,德国
富士通FUJITSU,日本
微软MICROSOFT,美国
北京汽车BEIJINGAUTOMOTIVEGROUP,中国
当用测试二中的代码处理这个新数据集时,得到的打印日志和输出文件信息分别如下:
打印日志
part-r-00000文件
part-r-00001文件
分析这些信息,首先是groups=10.新数据集中明明总共只有7个不同的国家,按道理来说,根据国家分组后应该得到groups=7才对。先不管,接着看输出的两个文件。第一个文件是中国区的统计数据,15表示这个分区中总共有15条记录,0表示这个分区中只有一个分组,都跟预计的结果一致。再看第二个文件,发现问题了!竟然没有将相同国家的聚合到相同分组中???虽然不知道哪里出了问题,但是也会发现一点猫腻。就是第一个美国分组中,显示有2条记录。而后接着就是日本分组了。因此做如下猜想:因为reduce端是先将所有map端对应的分区数据copy过来,然后合并成一个大文件。此后对这个大文件中的记录进行排序操作,而这个排序规则也是根据自定义键类中的compareTo()方法实现的,而这个比较规则是先比较公司后比较国家。也就是说,相同国家的公司中间很可能被其他国家的公司隔开了。而分组操作是从上往下一条一条记录进行比对来进行分组的。如果上下两条记录对应的组合键中的国家相同,则合并到一个组,如果不同,则上下各分一个组。这样就能解释part-r-00001的结果了。
为了进一步证明猜想,需要在输出中增加公司名,并且取消分组,看看输出结果跟猜想的符不符合。
下面是增加公司名,且取消分组时的输出文件part-r-00001:
完美有没有!!!完美证明了猜想的正确性!
分组详解:分组前先对合并后的分区文件中的记录进行排序,排序后再进行分组。分组是通过对排序后的记录从上往下遍历比对进行的。如果上下两个比对结果为0,则分到同一个组,否则各分一个组。后面的分组与前面的分组无关,只与紧挨着的上一条记录有关。也就是说,就算前面有一个“美国”分组,但是中间隔了一个“日本”分组,则后面再出现“美国”时也不会合并到前面的分组中!!!因此,如果想按照国家分组的话,应该将国家作为组合键的第一个属性,这样在reduce端排序后得到的就是相同的国家上下挨着了。
关于MapReduce的shuffle过程,请看这篇文章:https://mp.csdn.net/postedit/81233547