MapReduce聚合

mapreduce的核心思想就是:map读入一条记录,然后重置其key 、value;reduce针对map的同一条key,把其values整合,然后重新输出key、value;

这两天做mapreduce时,忽然有种想法:针对一行记录,是否有输出多行记录,包含有不同到key,value,的情况;这种情况到应用是否有好处呢?

假设有这样的一个数据:其中第一列代表用户的标识,第二列代表书籍的标识

1,A
1,B
1,C
2,A
2,B
3,B
3,C
4,A
4,E
5,C
5,E

问题是:要输出两个集合,分别是:针对用户1,输出看过A书的其他用户的集合,以及其他用户看过的其他书籍,比如:

1,A---》2,4;E,B

1,B---》3,2;A,C

。。。

那现在看下,我们可以如何操作呢?很直观的两种想法:一,以用户标识作为key,书籍标识作为value,那么经过mapreduce后,我们得到到数据如下:

1---》A,B,C

2---》A,B

。。。

或者以书籍标识作为key ,以用户标识作为value,那么我们得到的数据如下:

A--》1,2,4

B--》1,2,3

.。。

这两种情况都很难做到我们要的输出,那么如何做呢?就用文章开始说过的方法:一条记录产生多条key/value:

下面就具体代码进行分析:

第一个Mapper:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4. import org.apache.hadoop.io.LongWritable;  
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Mapper;  
  7. /* 
  8.  * first map input: 1,A 
  9.  * 1,A 
  10.  * 1,B 
  11.  * reduce output: 1 /t A,B,C, 
  12.  */  
  13. public class BookM extends Mapper<LongWritable,Text,Text,Text>{  
  14.     public void map(LongWritable key,Text line,Context context)throws IOException,InterruptedException{  
  15.         String[] values=line.toString().split(",");  
  16.         if(values.length!=2){  
  17.             return;  
  18.         }  
  19.         String userid=values[0];  
  20.         String bookid=values[1];  
  21.         context.write(new Text(userid), new Text(bookid));  
  22.     }  
  23. }  

第一个Reducer:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4.   
  5.   
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Reducer;  
  8.   
  9. public class BookR extends Reducer<Text,Text,Text,Text>{  
  10.     public void reduce(Text key,Iterable<Text> values,Context context)throws IOException,  
  11.         InterruptedException{  
  12.         StringBuffer sb=new StringBuffer();  
  13.         for(Text val:values){  
  14.             sb.append(val.toString());  
  15.             sb.append(",");  
  16.         }  
  17.         context.write(key, new Text(sb.toString()));  
  18.     }  
  19.   
  20. }  
第一个M/R的操作是输出用户标识和其所看过的所有书籍,这个和第一种操作一样,输出如下:

1    A,B,C,
2    A,B,
3    B,C,
4    A,E,
5    C,E,

接下来是第二个M/R:

第二个Mapper:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4. import org.apache.hadoop.io.LongWritable;  
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Mapper;  
  7.   
  8.  public class BookMM extends Mapper<LongWritable,Text,Text,Text>{  
  9.     public void map(LongWritable key,Text line,Context context)throws IOException,InterruptedException{  
  10.         String[] values=line.toString().split("\t");  
  11.         if(values.length!=2){  
  12.             return;  
  13.         }  
  14.         String userid=values[0];  
  15.         String bookid=values[1];  
  16.         String[] books=bookid.split(",");  
  17.         String fulluser=userid+":"+bookid;  
  18.         for(int i=0;i<books.length;i++){  
  19.             // every book and full userinfo  
  20.             context.write(new Text(books[i]), new Text(fulluser));  
  21.         }  
  22.     }  
  23. }  
第二个Reducer:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4. import org.apache.hadoop.io.Text;  
  5. import org.apache.hadoop.mapreduce.Reducer;  
  6.   
  7. public class BookRR extends Reducer<Text,Text,Text,Text>{  
  8.     public void reduce(Text key,Iterable<Text> values,Context context)throws IOException,  
  9.         InterruptedException{  
  10.         StringBuffer sb=new StringBuffer();  
  11.         for(Text val:values){  
  12.             sb.append(val.toString());  
  13.             sb.append("|");  
  14.         }  
  15.         context.write(key, new Text(sb.toString()));  
  16.     }  
  17. }  
这个map的输出像这个样子:

A--》1:A,B,C

B--》1:A,B,C

C--》1:A,B,C

A--》2:A,B

B--》2:A,B

。。。

特别要注意这里的针对一条记录产生了多条key/value,比如:针对:1--》A,B,C,产生了:

A--》1:A,B,C

B--》1:A,B,C

C--》1:A,B,C

这样的三条记录,这里是为了reduce操作做准备的,那么reduce操作就把每个book标识的所有用户的信息全部输出了,如下:

A    1:A,B,C,|2:A,B,|4:A,E,|
B    1:A,B,C,|2:A,B,|3:B,C,|
C    1:A,B,C,|3:B,C,|5:C,E,|
E    4:A,E,|5:C,E,|

接下来第三组M/R:

第三个Mapper:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4. import org.apache.hadoop.io.LongWritable;  
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Mapper;  
  7.   
  8. public class BookMMM extends Mapper<LongWritable,Text,Text,Text>{  
  9.     public void map(LongWritable key,Text line,Context context)throws IOException,InterruptedException{  
  10.         String[] values=line.toString().split("\t");  
  11.         if(values.length!=2){  
  12.             return;  
  13.         }  
  14.         String bookid=values[0];  
  15.         String fulluserinfo=values[1];  
  16.         String[] fullusersinfo=fulluserinfo.split("\\|");  
  17.         // data:   A    1:A,B,C,|2:A,B,|4:A,E,|    
  18.         // fullusersinfo = 1:A,B,C,|2:A,B,|4:A,E,|   
  19.         // out is 1,A  1:A,B,C,|2:A,B,|4:A,E,|   
  20.         //            2,A   1:A,B,C,|2:A,B,|4:A,E,|   
  21.         for(int i=0;i<fullusersinfo.length;i++){  
  22.             // every book and full userinfo  
  23.             String[] singleuser=fullusersinfo[i].split(":");  
  24.             String newkey=singleuser[0]+","+bookid; // newkey = 1,A  
  25.             context.write(new Text(newkey), line);  
  26.         }  
  27.     }  
  28. }  
第三个Recuer:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.HashSet;  
  5. import java.util.Iterator;  
  6. import java.util.Set;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Reducer;  
  9.   
  10. public class BookRRR extends Reducer<Text,Text,Text,Text>{  
  11.     public void reduce(Text key,Iterable<Text>  values,Context context)throws IOException,  
  12.         InterruptedException{  
  13.             // input  is 1,A  1:A,B,C,|2:A,B,|4:A,E,|   
  14.             //                2,A   1:A,B,C,|2:A,B,|4:A,E,|   
  15.           Set<String> allotherusers=new HashSet<String>();  
  16.           Set<String> otheruserbooks=new HashSet<String>();  
  17.         String[] keystr=key.toString().split(",");  
  18.         String useridkey=keystr[0];  // the userid in key  
  19.         Iterator<Text> iter=values.iterator();  
  20.         String[] tempval= iter.next().toString().split("\t");  
  21.         if(tempval.length!=2){  
  22.             return;  
  23.         }  
  24.         String val=tempval[1];  
  25.     //  String val=values.toString(); //val= 1:A,B,C,|2:A,B,|4:A,E,|  
  26.             String[] usersinfo=val.toString().split("\\|");  // usersinfo={"1:A,B,C,","2:A,B,","4:A,E,"}  
  27.             for(int i=0;i<usersinfo.length;i++){  
  28.                 String useridvalue=usersinfo[i].split(":")[0];   // usersinfo[0]= 1:A,B,C,  
  29.                 String[] books=usersinfo[i].split(":")[1].split(",");  // books=A,B,C  
  30.                 int songsnum=books.length;  
  31.                 if(useridkey.equals(useridvalue)){  
  32.                     ;  
  33.                 }else{  
  34.                     allotherusers.add(useridvalue);  //  add a user  
  35.                     for(int j=0;j<songsnum;j++){  
  36.                         otheruserbooks.add(books[j]);  
  37.                     }  
  38.                 }  
  39.             }     
  40.             // get rid of the keystr[a] both in currentusersongs and otherusersongs  
  41.             // there can ignore contains method?  
  42.   
  43.             if(otheruserbooks.contains(keystr[1])){  
  44.                 otheruserbooks.remove(keystr[1]);  
  45.             }     
  46.             // get the users and books  
  47.             Iterator<String> currentiter=allotherusers.iterator();  
  48.             Iterator<String>otheriter=otheruserbooks.iterator();  
  49.             StringBuffer sb1=new StringBuffer();  
  50. //          StringBuffer sb2=new StringBuffer();  
  51.             while(currentiter.hasNext()){  
  52.                 sb1.append(currentiter.next());  
  53.                 sb1.append(",");  
  54.             }  
  55.             sb1.append(":");  
  56.             while(otheriter.hasNext()){  
  57.                 sb1.append(otheriter.next());  
  58.                 sb1.append(",");  
  59.             }  
  60.             context.write(key, new Text(sb1.toString()));     
  61.     }  
  62. }  
第三个的map输出又使用到了针对一条记录产生多条key/value的结果,比如针对 :A    1:A,B,C,|2:A,B,|4:A,E,|  , 产生了如下的记录:

1,A    1:A,B,C,|2:A,B,|4:A,E,|

2,A    1:A,B,C,|2:A,B,|4:A,E,|

4,A    1:A,B,C,|2:A,B,|4:A,E,|

这个阶段的输出如下:

1,A    2,4,:E,B,
1,B    3,2,:A,C,
1,C    3,5,:E,B,
2,A    1,4,:E,B,C,
2,B    3,1,:A,C,
3,B    2,1,:A,C,
3,C    1,5,:E,A,B,
4,A    2,1,:B,C,
4,E    5,:C,
5,C    3,1,:A,B,
5,E    4,:A,

第三个阶段的reduce任务不需要把相同到key放入同一个组内,因为第三个map输出没有相同的Key,接下来的操作就是简单的把结果分离,得到我们想要到结果。

任务调度的代码如下:

[java] view plain copy
  1. package org.fansy.data907;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.conf.Configuration;  
  6. import org.apache.hadoop.fs.Path;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Job;  
  9. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  10. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  11. import org.apache.hadoop.util.GenericOptionsParser;  
  12.   
  13. public class Book {  
  14.     /** 
  15.      * use this to solve the book problem 
  16.      * first map input: 1,A 
  17.      * 1,A 
  18.      * 1,B 
  19.      * reduce output: 1 /t A,B,C, 
  20.      * @throws IOException  
  21.      * @throws ClassNotFoundException  
  22.      * @throws InterruptedException  
  23.      */  
  24.     public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {  
  25.         // TODO Auto-generated method stub    
  26.         // job one configuration  
  27.         Configuration conf = new Configuration();  
  28.         String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();  
  29.         if (otherArgs.length != 2) {  
  30.           System.err.println("Usage: Book <in> <out>");  
  31.           System.exit(2);  
  32.         }  
  33.         Job job = new Job(conf, "book job one");  
  34.         job.setJarByClass(Book.class);  
  35.         job.setMapperClass(BookM.class);  
  36.         job.setMapOutputKeyClass(Text.class);  
  37.         job.setMapOutputValueClass(Text.class);  
  38.         job.setReducerClass(BookR.class);  
  39.         job.setOutputKeyClass(Text.class);  
  40.         job.setOutputValueClass(Text.class);  
  41.         FileInputFormat.addInputPath(job, new Path(otherArgs[0]));  
  42.         FileOutputFormat.setOutputPath(job, new Path("hdfs://localhost:9000/user/fansy/out/out05"));  
  43.         if(!job.waitForCompletion(true)){  
  44.             System.exit(1); // run error then exit  
  45.         }  
  46.         /* 
  47.          *  next map and reduce 
  48.          *  map out:  
  49.          *  A 1:A,B,C 
  50.          *  B 1:A,B,C 
  51.          *  ... 
  52.          *  B 2:B,C 
  53.          *  reduce out: 
  54.          *     A    1:A,B,C,|2:A,B,|4:A,E,| 
  55.                 B   1:A,B,C,|2:A,B,|3:B,C,| 
  56.                 C   1:A,B,C,|3:B,C,|5:C,E,| 
  57.                 E   4:A,E,|5:C,E,|       
  58.          *  ... 
  59.          */  
  60.           
  61.         Configuration conf2 = new Configuration();  
  62.         Job job2 = new Job(conf2, "book job two");  
  63.         job2.setJarByClass(Book.class);  
  64.         job2.setMapperClass(BookMM.class);  
  65.         job2.setMapOutputKeyClass(Text.class);  
  66.         job2.setMapOutputValueClass(Text.class);  
  67.         job2.setReducerClass(BookRR.class);  
  68.         job2.setOutputKeyClass(Text.class);  
  69.         job2.setOutputValueClass(Text.class);  
  70.         FileInputFormat.addInputPath(job2, new Path("hdfs://localhost:9000/user/fansy/out/out05/part*"));  
  71.         FileOutputFormat.setOutputPath(job2, new Path("hdfs://localhost:9000/user/fansy/out/out06"));  
  72.         if(!job2.waitForCompletion(true)){  
  73.             System.exit(1); // run error then exit  
  74.         }  
  75.         /* 
  76.          * next map out: 
  77.          * 1A   1:A,B,C,|3:A,C,|4:A,E,| 
  78.          * 3A   1:A,B,C,|3:A,C,|4:A,E,| 
  79.          * 4A   1:A,B,C,|3:A,C,|4:A,E,| 
  80.          * 1B   1:A,B,C,|2:A,B,|3:B,C,| 
  81.          * 2B   1:A,B,C,|2:A,B,|3:B,C,| 
  82.          * ... 
  83.          * reduce :get the users and books 
  84.          *  
  85.          */  
  86.           
  87.         Configuration conf3 = new Configuration();  
  88.         Job job3 = new Job(conf3, "book job three ");  
  89.         job3.setJarByClass(Book.class);  
  90.         job3.setMapperClass(BookMMM.class);  
  91.         job3.setMapOutputKeyClass(Text.class);  
  92.         job3.setMapOutputValueClass(Text.class);  
  93.         job3.setReducerClass(BookRRR.class);  
  94.         job3.setOutputKeyClass(Text.class);  
  95.         job3.setOutputValueClass(Text.class);  
  96.         FileInputFormat.addInputPath(job3, new Path("hdfs://localhost:9000/user/fansy/out/out06/part*"));  
  97.         FileOutputFormat.setOutputPath(job3, new Path(otherArgs[1]));  
  98.         System.exit(job3.waitForCompletion(true) ? 0 : 1);  
  99.     }  
  100.   
  101. }  
至此,所有的操作完成,本文的核心思想也就是针对一条记录如何使用map设置不同key/value,然后使用reduce根据map的输出整合结果而已。
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页