Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)

1.  wordcount程序

先以简单的wordcount为例。

Mapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.wordcount;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.io.IntWritable;  
  7. import org.apache.hadoop.io.LongWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Mapper;  
  10.   
  11. //4个泛型中,前两个是指定mapper输入数据的类型,KEYIN是输入的key的类型,VALUEIN是输入的value的类型  
  12. //map 和 reduce 的数据输入输出都是以 key-value对的形式封装的  
  13. //默认情况下,Map框架传递给我们的mapper的输入数据中,key是要处理的文本中一行的起始偏移量(选用LongWritable),value是这一行的内容(VALUEIN选用Text)  
  14. //在wordcount中,经过mapper处理数据后,得到的是<单词,1>这样的结果,所以KEYOUT选用Text,VAULEOUT选用IntWritable  
  15. public class WCMapper extends Mapper {  
  16.   
  17.     // MapReduce框架每读一行数据就调用一次map方法  
  18.     @Override  
  19.     protected void map(LongWritable k1, Text v1,  
  20.             Mapper.Context context)  
  21.             throws IOException, InterruptedException {  
  22.         // 将这一行的内容转换成string类型  
  23.         String line = v1.toString();  
  24.         // 对这一行的文本按特定分隔符切分  
  25.         // String[] words = line.split(" ");  
  26.         String[] words = StringUtils.split(line, " ");  
  27.         // 遍历这个单词数组,输出为key-value形式 key:单词 value : 1  
  28.         for (String word : words) {  
  29.             context.write(new Text(word), new IntWritable(1));  
  30.         }  
  31.   
  32.     }  
  33.   
  34. }  

Reducer:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.wordcount;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.IntWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Reducer;  
  8.   
  9. //经过mapper处理后的数据会被reducer拉取过来,所以reducer的KEYIN、VALUEIN和mapper的KEYOUT、VALUEOUT一致  
  10. //经过reducer处理后的数据格式为<单词,频数>,所以KEYOUT为Text,VALUEOUT为IntWritable  
  11. public class WCReducer extends Reducer {  
  12.   
  13.     // 当mapper框架将相同的key的数据处理完成后,reducer框架会将mapper框架输出的数据变成  
  14.     // 例如,在wordcount中会将mapper框架输出的所有变为,即这里的,然后将作为reduce函数的输入  
  15.     @Override  
  16.     protected void reduce(Text k2, Iterable v2s,  
  17.             Reducer.Context context)  
  18.             throws IOException, InterruptedException {  
  19.         int count = 0;  
  20.         // 遍历v2的list,进行累加求和  
  21.         for (IntWritable v2 : v2s) {  
  22.             count = v2.get();  
  23.         }  
  24.         // 输出这一个单词的统计结果  
  25.         context.write(k2, new IntWritable(count));  
  26.     }  
  27.   
  28. }  

驱动类:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.wordcount;  
  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.IntWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  12.   
  13. /** 
  14.  * 用来描述一个特定的作业 比如,该作业使用哪个类作为逻辑处理中的map,哪个作为reduce 还可以指定该作业要处理的数据所在的路径 
  15.  * 还可以指定改作业输出的结果放到哪个路径 
  16.  *  
  17.  * @author Oner.wv 
  18.  * 
  19.  */  
  20. public class WCRunner {  
  21.     public static void main(String[] args) throws ClassNotFoundException,  
  22.             InterruptedException, IOException {  
  23.   
  24.         Configuration conf = new Configuration();  
  25.         Job wcJob = Job.getInstance(conf);  
  26.   
  27.         // 设置job所在的类在哪个jar包  
  28.         wcJob.setJarByClass(WCRunner.class);  
  29.   
  30.         // 指定job所用的mappe类和reducer类  
  31.         wcJob.setMapperClass(WCMapper.class);  
  32.         wcJob.setReducerClass(WCReducer.class);  
  33.   
  34.         // 指定mapper输出类型和reducer输出类型  
  35.         // 由于在wordcount中mapper和reducer的输出类型一致,  
  36.         // 所以使用setOutputKeyClass和setOutputValueClass方法可以同时设定mapper和reducer的输出类型  
  37.         // 如果mapper和reducer的输出类型不一致时,可以使用setMapOutputKeyClass和setMapOutputValueClass单独设置mapper的输出类型  
  38.         // wcJob.setMapOutputKeyClass(Text.class);  
  39.         // wcJob.setMapOutputValueClass(IntWritable.class);  
  40.         wcJob.setOutputKeyClass(Text.class);  
  41.         wcJob.setOutputValueClass(IntWritable.class);  
  42.   
  43.         // 指定job处理的数据路径  
  44.         FileInputFormat.setInputPaths(wcJob, new Path(  
  45.                 "hdfs://master:9000/user/exe_mapreduce/wordcount/input"));  
  46.         // 指定job处理数据输出结果的路径  
  47.         FileOutputFormat.setOutputPath(wcJob, new Path(  
  48.                 "hdfs://master:9000/user/exe_mapreduce/wordcount/output"));  
  49.   
  50.         // 将job提交给集群运行  
  51.         wcJob.waitForCompletion(true);  
  52.     }  
  53. }  


2. 统计手机流量信息

    从下面的数据中的得到每个手机号的上行流量、下行流量、总流量。

源数据:

[html]  view plain  copy
 
  1. 1363157985066   13726230503 00-FD-07-A4-72-B8:CMCC  120.196.100.82  i02.c.aliimg.com        24  27  2481    24681   200  
  2. 1363157995052   13826544101 5C-0E-8B-C7-F1-E0:CMCC  120.197.40.4            4   0   264 0   200  
  3. 1363157991076   13926435656 20-10-7A-28-CC-0A:CMCC  120.196.100.99          2   4   132 1512    200  
  4. 1363154400022   13926251106 5C-0E-8B-8B-B1-50:CMCC  120.197.40.4            4   0   240 0   200  
  5. 1363157993044   18211575961 94-71-AC-CD-E6-18:CMCC-EASY 120.196.100.99  iface.qiyi.com  视频网站    15  12  1527    2106    200  
  6. 1363157995074   84138413    5C-0E-8B-8C-E8-20:7DaysInn  120.197.40.4    122.72.52.12        20  16  4116    1432    200  
  7. 1363157993055   13560439658 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          18  15  1116    954 200  
  8. 1363157995033   15920133257 5C-0E-8B-C7-BA-20:CMCC  120.197.40.4    sug.so.360.cn   信息安全    20  20  3156    2936    200  
  9. 1363157983019   13719199419 68-A1-B7-03-07-B1:CMCC-EASY 120.196.100.82          4   0   240 0   200  
  10. 1363157984041   13660577991 5C-0E-8B-92-5C-20:CMCC-EASY 120.197.40.4    s19.cnzz.com    站点统计    24  9   6960    690 200  
  11. 1363157973098   15013685858 5C-0E-8B-C7-F7-90:CMCC  120.197.40.4    rank.ie.sogou.com   搜索引擎    28  27  3659    3538    200  
  12. 1363157986029   15989002119 E8-99-C4-4E-93-E0:CMCC-EASY 120.196.100.99  www.umeng.com   站点统计    3   3   1938    180 200  
  13. 1363157992093   13560439658 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          15  9   918 4938    200  
  14. 1363157986041   13480253104 5C-0E-8B-C7-FC-80:CMCC-EASY 120.197.40.4            3   3   180 180 200  
  15. 1363157984040   13602846565 5C-0E-8B-8B-B6-00:CMCC  120.197.40.4    2052.flash2-http.qq.com 综合门户    15  12  1938    2910    200  
  16. 1363157995093   13922314466 00-FD-07-A2-EC-BA:CMCC  120.196.100.82  img.qfc.cn      12  12  3008    3720    200  
  17. 1363157982040   13502468823 5C-0A-5B-6A-0B-D4:CMCC-EASY 120.196.100.99  y0.ifengimg.com 综合门户    57  102 7335    110349  200  
  18. 1363157986072   18320173382 84-25-DB-4F-10-1A:CMCC-EASY 120.196.100.99  input.shouji.sogou.com  搜索引擎    21  18  9531    2412    200  
  19. 1363157990043   13925057413 00-1F-64-E1-E6-9A:CMCC  120.196.100.55  t3.baidu.com    搜索引擎    69  63  11058   48243   200  
  20. 1363157988072   13760778710 00-FD-07-A4-7B-08:CMCC  120.196.100.82          2   2   120 120 200  
  21. 1363157985066   13726238888 00-FD-07-A4-72-B8:CMCC  120.196.100.82  i02.c.aliimg.com        24  27  2481    24681   200  
  22. 1363157993055   13560436666 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          18  15  1116    954 200  


数据格式为:

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第1张图片

想要的到的数据格式为:

[html]  view plain  copy
 
  1. 手机号 上行流量    下行流量    总流量  
  2. 13726230503     2481    24681   27162  
  3. 13826544101     264     0   264  
  4. 13926435656     132     1512    1644  
  5. ...             ...     ...  

2.1 引入和Hadoop序列化机制相关的mapreduce

     由于源数据中每一个手机号可能存在多条上网记录,最后要得到的输出格式是一个手机号的所有上行流量、下行流量和总流量。所以可以考虑利用MapReduce框架的特性,将每个手机号作为map的输出key,该手机号上网信息作为map的输出value,经过shuffle,则在reduce端接收到一个,其中,key手机号,value-list为该手机号所对应的一些上网信息的集合。这里有一个问题,由于map和reduce输入输出都为key-value键值对形式,所以必须将手机的上网信息(上行流量、下行流量)封装成一个Bean类,将这个类作为value。

    由于数据需要在不同的节点间进行网络传输,所以Bean类必须实现序列化和反序列化,Hadoop提供了一套序列化机制(实现Writable接口)

FlowBean:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsum;  
  2.   
  3. import java.io.DataInput;  
  4. import java.io.DataOutput;  
  5. import java.io.IOException;  
  6.   
  7. import org.apache.hadoop.io.Writable;  
  8.   
  9. public class FlowBean implements Writable {  
  10.   
  11.     private String phoneNB;  
  12.     private long up_flow;  
  13.     private long down_flow;  
  14.     private long sum_flow;  
  15.   
  16.     // 在反序列化时,反射机制需要调用空参构造函数,所以显示定义了一个空参构造函数  
  17.     public FlowBean() {  
  18.     }  
  19.   
  20.     // 为了对象数据的初始化方便,加入一个带参的构造函数  
  21.     public FlowBean(String phoneNB, long up_flow, long down_flow) {  
  22.         this.phoneNB = phoneNB;  
  23.         this.up_flow = up_flow;  
  24.         this.down_flow = down_flow;  
  25.         this.sum_flow = up_flow + down_flow;  
  26.     }  
  27.   
  28.     // 将对象的数据序列化到流中  
  29.     @Override  
  30.     public void write(DataOutput out) throws IOException {  
  31.         out.writeUTF(phoneNB);  
  32.         out.writeLong(up_flow);  
  33.         out.writeLong(down_flow);  
  34.         out.writeLong(sum_flow);  
  35.     }  
  36.   
  37.     // 从流中反序列化出对象的数据  
  38.     // 从数据流中读出对象字段时,必须跟序列化时的顺序保持一致  
  39.     @Override  
  40.     public void readFields(DataInput in) throws IOException {  
  41.         this.phoneNB = in.readUTF();  
  42.         this.up_flow = in.readLong();  
  43.         this.down_flow = in.readLong();  
  44.         this.sum_flow = in.readLong();  
  45.     }  
  46.   
  47.     public String getPhoneNB() {  
  48.         return phoneNB;  
  49.     }  
  50.   
  51.     public void setPhoneNB(String phoneNB) {  
  52.         this.phoneNB = phoneNB;  
  53.     }  
  54.   
  55.     public long getUp_flow() {  
  56.         return up_flow;  
  57.     }  
  58.   
  59.     public void setUp_flow(long up_flow) {  
  60.         this.up_flow = up_flow;  
  61.     }  
  62.   
  63.     public long getDown_flow() {  
  64.         return down_flow;  
  65.     }  
  66.   
  67.     public void setDown_flow(long down_flow) {  
  68.         this.down_flow = down_flow;  
  69.     }  
  70.   
  71.     public long getSum_flow() {  
  72.         return sum_flow;  
  73.     }  
  74.   
  75.     public void setSum_flow(long sum_flow) {  
  76.         this.sum_flow = sum_flow;  
  77.     }  
  78.   
  79.     @Override  
  80.     public String toString() {  
  81.         return "" + up_flow + "\t" + down_flow + "\t" + sum_flow;  
  82.     }  
  83. }  


FlowSumMapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsum;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.io.LongWritable;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Mapper;  
  9.   
  10. public class FlowSumMapper extends Mapper {  
  11.       
  12.     @Override  
  13.     protected void map(LongWritable k1, Text v1,  
  14.             Mapper.Context context)  
  15.             throws IOException, InterruptedException {  
  16.         // 一行数据  
  17.         String line = v1.toString();  
  18.         // 切分数据  
  19.         String[] fields = StringUtils.split(line, "\t");  
  20.         // 得到想要的手机号、上行流量、下行流量  
  21.         String phoneNB = fields[1];  
  22.         long up_flow = Long.parseLong(fields[7]);  
  23.         long down_flow = Long.parseLong(fields[8]);  
  24.         // 封装数据为kv并输出  
  25.         context.write(new Text(phoneNB), new FlowBean(phoneNB, up_flow,  
  26.                 down_flow));  
  27.   
  28.     }  
  29. }  


FlowSumReducer:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsum;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Reducer;  
  7.   
  8. public class FlowSumReducer extends Reducer {  
  9.   
  10.     // 框架每传递一组数据<1387788654,{flowbean,flowbean,flowbean,flowbean.....}>调用一次我们的reduce方法  
  11.     // reduce中的业务逻辑就是遍历values,然后进行累加求和再输出  
  12.     @Override  
  13.     protected void reduce(Text k2, Iterable v2s,  
  14.             Reducer.Context context)  
  15.             throws IOException, InterruptedException {  
  16.         long up_flow = 0;  
  17.         long down_flow = 0;  
  18.   
  19.         for (FlowBean v2 : v2s) {  
  20.             up_flow += v2.getUp_flow();  
  21.             down_flow += v2.getDown_flow();  
  22.         }  
  23.   
  24.         context.write(k2, new FlowBean(k2.toString(), up_flow, down_flow));  
  25.     }  
  26. }  


FlowSumRunner:

Job描述和提交的规范写法如下:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsum;  
  2.   
  3. import org.apache.hadoop.conf.Configuration;  
  4. import org.apache.hadoop.conf.Configured;  
  5. import org.apache.hadoop.fs.Path;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Job;  
  8. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  9. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  10. import org.apache.hadoop.util.Tool;  
  11. import org.apache.hadoop.util.ToolRunner;  
  12.   
  13. //这是job描述和提交类的规范写法  
  14. public class FlowSumRunner extends Configured implements Tool {  
  15.   
  16.     public static void main(String[] args) throws Exception {  
  17.         int res = ToolRunner  
  18.                 .run(new Configuration(), new FlowSumRunner(), args);  
  19.         System.exit(res);  
  20.     }  
  21.   
  22.     @Override  
  23.     public int run(String[] args) throws Exception {  
  24.         Configuration conf = new Configuration();  
  25.         Job job = Job.getInstance(conf);  
  26.   
  27.         job.setJarByClass(FlowSumRunner.class);  
  28.   
  29.         job.setMapperClass(FlowSumMapper.class);  
  30.         job.setReducerClass(FlowSumReducer.class);  
  31.   
  32.         // job.setMapOutputKeyClass(Text.class);  
  33.         // job.setMapOutputValueClass(FlowBean.class);  
  34.   
  35.         job.setOutputKeyClass(Text.class);  
  36.         job.setOutputValueClass(FlowBean.class);  
  37.   
  38.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  39.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  40.   
  41.         // 执行成功,返回0,否则返回1  
  42.         return job.waitForCompletion(true) ? 0 : 1;  
  43.     }  
  44. }  

    打成jar包后运行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop jar flowcount.jar cn.nuc.hadoop.mapreduce.flowsum.FlowSumRunner /user/exe_mapreduce/flowcount/input /user/exe_mapreduce/flowcount/output  

    查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/output/part-r-00000  
  2. 13480253104 180 200 380  
  3. 13502468823 102 7335    7437  
  4. 13560436666 954 200 1154  
  5. 13560439658 5892    400 6292  
  6. 13602846565 12  1938    1950  
  7. 13660577991 9   6960    6969  
  8. 13719199419 0   200 200  
  9. 13726230503 2481    24681   27162  
  10. 13726238888 2481    24681   27162  
  11. 13760778710 120 200 320  
  12. 13826544101 0   200 200  
  13. 13922314466 3008    3720    6728  
  14. 13925057413 63  11058   11121  
  15. 13926251106 0   200 200  
  16. 13926435656 1512    200 1712  
  17. 15013685858 27  3659    3686  
  18. 15920133257 20  3156    3176  
  19. 15989002119 3   1938    1941  
  20. 18211575961 12  1527    1539  
  21. 18320173382 18  9531    9549  
  22. 84138413    4116    1432    5548  

2.2  引入hadoop自定义排序

    从上面得到的结果可以看出来,hadoop默认将结果按照mapper的输出按照key来进行排序,如果我们想要自定义排序结果(比如按照总流量从高到低排序),该如何做呢?了解shuffle的都知道,shuffle过程中,会将map的输出结果按照key进行排序,所以只需要将FlowBean作为map输出的key值,前提是FlowBean实现了Comparable接口。在hadoop中既实现Writable接口,又实现Comparable接口,可以简写为实现了WritableComparable接口。

FlowBean:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsort;  
  2.   
  3. import java.io.DataInput;  
  4. import java.io.DataOutput;  
  5. import java.io.IOException;  
  6.   
  7. import org.apache.hadoop.io.WritableComparable;  
  8.   
  9. public class FlowBean implements WritableComparable {  
  10.   
  11.     private String phoneNB;  
  12.     private long up_flow;  
  13.     private long down_flow;  
  14.     private long sum_flow;  
  15.   
  16.     // 在反序列化时,反射机制需要调用空参构造函数,所以显示定义了一个空参构造函数  
  17.     public FlowBean() {  
  18.     }  
  19.   
  20.     // 为了对象数据的初始化方便,加入一个带参的构造函数  
  21.     public FlowBean(String phoneNB, long up_flow, long down_flow) {  
  22.         this.phoneNB = phoneNB;  
  23.         this.up_flow = up_flow;  
  24.         this.down_flow = down_flow;  
  25.         this.sum_flow = up_flow + down_flow;  
  26.     }  
  27.   
  28.     // 将对象的数据序列化到流中  
  29.     @Override  
  30.     public void write(DataOutput out) throws IOException {  
  31.         out.writeUTF(phoneNB);  
  32.         out.writeLong(up_flow);  
  33.         out.writeLong(down_flow);  
  34.         out.writeLong(sum_flow);  
  35.     }  
  36.   
  37.     // 从流中反序列化出对象的数据  
  38.     // 从数据流中读出对象字段时,必须跟序列化时的顺序保持一致  
  39.     @Override  
  40.     public void readFields(DataInput in) throws IOException {  
  41.         this.phoneNB = in.readUTF();  
  42.         this.up_flow = in.readLong();  
  43.         this.down_flow = in.readLong();  
  44.         this.sum_flow = in.readLong();  
  45.     }  
  46.   
  47.     public String getPhoneNB() {  
  48.         return phoneNB;  
  49.     }  
  50.   
  51.     public void setPhoneNB(String phoneNB) {  
  52.         this.phoneNB = phoneNB;  
  53.     }  
  54.   
  55.     public long getUp_flow() {  
  56.         return up_flow;  
  57.     }  
  58.   
  59.     public void setUp_flow(long up_flow) {  
  60.         this.up_flow = up_flow;  
  61.     }  
  62.   
  63.     public long getDown_flow() {  
  64.         return down_flow;  
  65.     }  
  66.   
  67.     public void setDown_flow(long down_flow) {  
  68.         this.down_flow = down_flow;  
  69.     }  
  70.   
  71.     public long getSum_flow() {  
  72.         return sum_flow;  
  73.     }  
  74.   
  75.     public void setSum_flow(long sum_flow) {  
  76.         this.sum_flow = sum_flow;  
  77.     }  
  78.   
  79.     @Override  
  80.     public String toString() {  
  81.         return "" + up_flow + "\t" + down_flow + "\t" + sum_flow;  
  82.     }  
  83.   
  84.     // 实现Comparable接口,需要复写compareTo方法  
  85.     @Override  
  86.     public int compareTo(FlowBean o) {  
  87.         return this.sum_flow > o.sum_flow ? -1 : 1;  
  88.     }  
  89. }  


SortMapReduce:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.flowsort;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.conf.Configuration;  
  7. import org.apache.hadoop.fs.Path;  
  8. import org.apache.hadoop.io.LongWritable;  
  9. import org.apache.hadoop.io.NullWritable;  
  10. import org.apache.hadoop.io.Text;  
  11. import org.apache.hadoop.mapreduce.Job;  
  12. import org.apache.hadoop.mapreduce.Mapper;  
  13. import org.apache.hadoop.mapreduce.Reducer;  
  14. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  15. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  16.   
  17. public class SortMapReduce {  
  18.   
  19.     public static class SortMapper extends  
  20.             Mapper {  
  21.         @Override  
  22.         protected void map(  
  23.                 LongWritable k1,  
  24.                 Text v1,  
  25.                 Mapper.Context context)  
  26.                 throws IOException, InterruptedException {  
  27.               
  28.             String line = v1.toString();  
  29.             String[] fields = StringUtils.split(line, "\t");  
  30.   
  31.             String phoneNB = fields[0];  
  32.             long up_flow = Long.parseLong(fields[1]);  
  33.             long down_flow = Long.parseLong(fields[2]);  
  34.   
  35.             context.write(new FlowBean(phoneNB, up_flow, down_flow),  
  36.                     NullWritable.get());  
  37.         }  
  38.     }  
  39.   
  40.     public static class SortReducer extends  
  41.             Reducer {  
  42.         @Override  
  43.         protected void reduce(FlowBean k2, Iterable v2s,  
  44.                 Reducer.Context context)  
  45.                 throws IOException, InterruptedException {  
  46.             String phoneNB = k2.getPhoneNB();  
  47.             context.write(new Text(phoneNB), k2);  
  48.         }  
  49.     }  
  50.   
  51.     public static void main(String[] args) throws IOException,  
  52.             ClassNotFoundException, InterruptedException {  
  53.   
  54.         Configuration conf = new Configuration();  
  55.         Job job = Job.getInstance(conf);  
  56.   
  57.         job.setJarByClass(SortMapReduce.class);  
  58.   
  59.         job.setMapperClass(SortMapper.class);  
  60.         job.setReducerClass(SortReducer.class);  
  61.   
  62.         job.setMapOutputKeyClass(FlowBean.class);  
  63.         job.setMapOutputValueClass(NullWritable.class);  
  64.   
  65.         job.setOutputKeyClass(Text.class);  
  66.         job.setOutputValueClass(FlowBean.class);  
  67.   
  68.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  69.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  70.   
  71.         System.exit(job.waitForCompletion(true) ? 0 : 1);  
  72.     }  
  73. }  


    打成jar包,运行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop jar flowcountsort.jar cn.nuc.hadoop.mapreduce.flowsort.SortMapReduce /user/exe_mapreduce/flowcount/output /user/exe_mapreduce/flowcount/sortout/  

    查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/sortout/part-r-00000  
  2. 13726238888 2481    24681   27162  
  3. 13726230503 2481    24681   27162  
  4. 13925057413 63  11058   11121  
  5. 18320173382 18  9531    9549  
  6. 13502468823 102 7335    7437  
  7. 13660577991 9   6960    6969  
  8. 13922314466 3008    3720    6728  
  9. 13560439658 5892    400 6292  
  10. 84138413    4116    1432    5548  
  11. 15013685858 27  3659    3686  
  12. 15920133257 20  3156    3176  
  13. 13602846565 12  1938    1950  
  14. 15989002119 3   1938    1941  
  15. 13926435656 1512    200 1712  
  16. 18211575961 12  1527    1539  
  17. 13560436666 954 200 1154  
  18. 13480253104 180 200 380  
  19. 13760778710 120 200 320  
  20. 13826544101 0   200 200  
  21. 13926251106 0   200 200  
  22. 13719199419 0   200 200  

2.3 引入Hadoop分区功能

     如果信息特别多,想要将最后的结果分别存放在不通过的文件中,该怎么办呢?可以使用Hadoop提供的Partitioner函数,hadoop默认使用HashPartitioner。可以查看下Hadoop源码:


[java]  view plain  copy
 
  1. public class HashPartitioner extends Partitioner {  
  2.   
  3.   /** Use {@link Object#hashCode()} to partition. */  
  4.   public int getPartition(K key, V value,  
  5.                           int numReduceTasks) {  
  6.     return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;  
  7.   }  
  8.   
  9. }  

    HashPartitioner是处理Mapper任务输出的,getPartition()方法有三个形参,key、value分别指的是Mapper任务的输出,numReduceTasks指的是设置的Reducer任务数量,默认值是1。那么任何整数与1相除的余数肯定是0。也就是说getPartition(…)方法的返回值总是0。也就是Mapper任务的输出总是送给一个Reducer任务,最终只能输出到一个文件中。据此分析,如果想要最终输出到多个文件中,在Mapper任务中对数据应该划分到多个区中。

AreaPartitioner

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.areapartition;  
  2.   
  3. import java.util.HashMap;  
  4.   
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Partitioner;  
  7.   
  8. public class AreaPartitioner extends Partitioner {  
  9.   
  10.     private static HashMap areaMap = new HashMap<>();  
  11.   
  12.     static {  
  13.         areaMap.put("135"0);  
  14.         areaMap.put("136"1);  
  15.         areaMap.put("137"2);  
  16.         areaMap.put("138"3);  
  17.         areaMap.put("139"4);  
  18.     }  
  19.   
  20.     @Override  
  21.     public int getPartition(Text key, FlowBean value, int numPartitions) {  
  22.         // 从key中拿到手机号,查询手机归属地字典,不同的省份返回不同的组号  
  23.         Integer areCoder = areaMap.get(key.toString().substring(03));  
  24.         if (areCoder == null) {  
  25.             areCoder = 5;  
  26.         }  
  27.         return areCoder;  
  28.     }  
  29.   
  30. }  


FlowBean

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.areapartition;  
  2.   
  3. import java.io.DataInput;  
  4. import java.io.DataOutput;  
  5. import java.io.IOException;  
  6.   
  7. import org.apache.hadoop.io.WritableComparable;  
  8.   
  9. public class FlowBean implements WritableComparable {  
  10.   
  11.     private String phoneNB;  
  12.     private long up_flow;  
  13.     private long down_flow;  
  14.     private long sum_flow;  
  15.   
  16.     // 在反序列化时,反射机制需要调用空参构造函数,所以显示定义了一个空参构造函数  
  17.     public FlowBean() {  
  18.     }  
  19.   
  20.     // 为了对象数据的初始化方便,加入一个带参的构造函数  
  21.     public FlowBean(String phoneNB, long up_flow, long down_flow) {  
  22.         this.phoneNB = phoneNB;  
  23.         this.up_flow = up_flow;  
  24.         this.down_flow = down_flow;  
  25.         this.sum_flow = up_flow + down_flow;  
  26.     }  
  27.   
  28.     // 将对象的数据序列化到流中  
  29.     @Override  
  30.     public void write(DataOutput out) throws IOException {  
  31.         out.writeUTF(phoneNB);  
  32.         out.writeLong(up_flow);  
  33.         out.writeLong(down_flow);  
  34.         out.writeLong(sum_flow);  
  35.     }  
  36.   
  37.     // 从流中反序列化出对象的数据  
  38.     // 从数据流中读出对象字段时,必须跟序列化时的顺序保持一致  
  39.     @Override  
  40.     public void readFields(DataInput in) throws IOException {  
  41.         this.phoneNB = in.readUTF();  
  42.         this.up_flow = in.readLong();  
  43.         this.down_flow = in.readLong();  
  44.         this.sum_flow = in.readLong();  
  45.     }  
  46.   
  47.     public String getPhoneNB() {  
  48.         return phoneNB;  
  49.     }  
  50.   
  51.     public void setPhoneNB(String phoneNB) {  
  52.         this.phoneNB = phoneNB;  
  53.     }  
  54.   
  55.     public long getUp_flow() {  
  56.         return up_flow;  
  57.     }  
  58.   
  59.     public void setUp_flow(long up_flow) {  
  60.         this.up_flow = up_flow;  
  61.     }  
  62.   
  63.     public long getDown_flow() {  
  64.         return down_flow;  
  65.     }  
  66.   
  67.     public void setDown_flow(long down_flow) {  
  68.         this.down_flow = down_flow;  
  69.     }  
  70.   
  71.     public long getSum_flow() {  
  72.         return sum_flow;  
  73.     }  
  74.   
  75.     public void setSum_flow(long sum_flow) {  
  76.         this.sum_flow = sum_flow;  
  77.     }  
  78.   
  79.     @Override  
  80.     public String toString() {  
  81.         return "" + up_flow + "\t" + down_flow + "\t" + sum_flow;  
  82.     }  
  83.   
  84.     // 实现Comparable接口,需要复写compareTo方法  
  85.     @Override  
  86.     public int compareTo(FlowBean o) {  
  87.         return this.sum_flow > o.sum_flow ? -1 : 1;  
  88.     }  
  89. }  

FlowSumArea 


[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.areapartition;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.conf.Configuration;  
  7. import org.apache.hadoop.fs.Path;  
  8. import org.apache.hadoop.io.LongWritable;  
  9. import org.apache.hadoop.io.Text;  
  10. import org.apache.hadoop.mapreduce.Job;  
  11. import org.apache.hadoop.mapreduce.Mapper;  
  12. import org.apache.hadoop.mapreduce.Reducer;  
  13. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  14. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  15.   
  16. /** 
  17.  * 对流量原始日志进行流量统计,将不同省份的用户统计结果输出到不同文件 需要自定义改造两个机制: 1、改造分区的逻辑,自定义一个partitioner 
  18.  * 2、自定义reduer task的并发任务数 
  19.  *  
  20.  * @author [email protected] 
  21.  * 
  22.  */  
  23. public class FlowSumArea {  
  24.   
  25.     public static class FlowSumAreaMapper extends  
  26.             Mapper {  
  27.   
  28.         @Override  
  29.         protected void map(LongWritable k1, Text v1,  
  30.                 Mapper.Context context)  
  31.                 throws IOException, InterruptedException {  
  32.             String line = v1.toString();  
  33.             String[] fields = StringUtils.split(line, "\t");  
  34.   
  35.             String phoneNB = fields[1];  
  36.             Long up_flow = Long.parseLong(fields[7]);  
  37.             Long down_flow = Long.parseLong(fields[8]);  
  38.   
  39.             context.write(new Text(phoneNB), new FlowBean(phoneNB, up_flow,  
  40.                     down_flow));  
  41.         }  
  42.     }  
  43.   
  44.     public static class FlowSumAreaReducer extends  
  45.             Reducer {  
  46.   
  47.         @Override  
  48.         protected void reduce(Text k2, Iterable v2s,  
  49.                 Reducer.Context context)  
  50.                 throws IOException, InterruptedException {  
  51.             long up_flow = 0;  
  52.             long down_flow = 0;  
  53.             for (FlowBean v2 : v2s) {  
  54.                 up_flow += v2.getUp_flow();  
  55.                 down_flow += v2.getDown_flow();  
  56.             }  
  57.             context.write(new Text(k2), new FlowBean(k2.toString(), up_flow,  
  58.                     down_flow));  
  59.         }  
  60.     }  
  61.   
  62.     public static void main(String[] args) throws IOException,  
  63.             ClassNotFoundException, InterruptedException {  
  64.         Configuration conf = new Configuration();  
  65.         Job job = Job.getInstance(conf);  
  66.   
  67.         job.setJarByClass(FlowSumArea.class);  
  68.   
  69.         job.setMapperClass(FlowSumAreaMapper.class);  
  70.         job.setReducerClass(FlowSumAreaReducer.class);  
  71.   
  72.         // 定义分组逻辑类  
  73.         job.setPartitionerClass(AreaPartitioner.class);  
  74.   
  75.         job.setOutputKeyClass(Text.class);  
  76.         job.setOutputValueClass(FlowBean.class);  
  77.   
  78.         // 设定reducer的任务并发数,应该跟分组的数量保持一致  
  79.         job.setNumReduceTasks(6);  
  80.   
  81.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  82.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  83.           
  84.         System.exit(job.waitForCompletion(true) ? 0 : 1);  
  85.     }  
  86. }  

     打包运行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop jar area.jar cn.nuc.hadoop.mapreduce.areapartition.FlowSumArea /user/exe_mapreduce/flowcount/input /user/exe_mapreduce/flowcount/areaout  

     查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -ls /user/exe_mapreduce/flowcount/areaout/  
  2. Found 7 items  
  3. -rw-r--r--   3 hadoop supergroup          0 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/_SUCCESS  
  4. -rw-r--r--   3 hadoop supergroup         77 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00000  
  5. -rw-r--r--   3 hadoop supergroup         49 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00001  
  6. -rw-r--r--   3 hadoop supergroup        104 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00002  
  7. -rw-r--r--   3 hadoop supergroup         22 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00003  
  8. -rw-r--r--   3 hadoop supergroup        102 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00004  
  9. -rw-r--r--   3 hadoop supergroup        172 2016-02-07 19:28 /user/exe_mapreduce/flowcount/areaout/part-r-00005  

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00000  
  2. 13502468823 102 7335    7437  
  3. 13560436666 954 200 1154  
  4. 13560439658 5892    400 6292  
  5. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00001  
  6. 13602846565 12  1938    1950  
  7. 13660577991 9   6960    6969  
  8. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00002  
  9. 13719199419 0   200 200  
  10. 13726230503 2481    24681   27162  
  11. 13726238888 2481    24681   27162  
  12. 13760778710 120 200 320  
  13. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00003  
  14. ^[[A13826544101 0   200 200  
  15. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00004  
  16. ^[[A13922314466 3008    3720    6728  
  17. 13925057413 63  11058   11121  
  18. 13926251106 0   200 200  
  19. 13926435656 1512    200 1712  
  20. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/flowcount/areaout/part-r-00005  
  21. 13480253104 180 200 380  
  22. 15013685858 27  3659    3686  
  23. 15920133257 20  3156    3176  
  24. 15989002119 3   1938    1941  
  25. 18211575961 12  1527    1539  
  26. 18320173382 18  9531    9549  
  27. 84138413    4116    1432    5548  


3. 数据去重

“ 数据去重”主要是为了掌握和利用并行化思想来对数据进行有意义的筛选。 统计大 数据集上的数据种类个数、 从网站日志中计算访问地等这些看似庞杂的任务都会涉及数据 去重。下面就进入这个实例的 MapReduce 程序设计。

3.1 实例描述

对数据文件中的数据进行去重。数据文件中的每行都是一个数据。

样例输入如下所示:

file1:

[plain]  view plain  copy
 
  1. 2012-3-1 a  
  2. 2012-3-2 b  
  3. 2012-3-3 c  
  4. 2012-3-4 d  
  5. 2012-3-5 a  
  6. 2012-3-6 b  
  7. 2012-3-7 c  
  8. 2012-3-3 c  

file2:

[plain]  view plain  copy
 
  1. 2012-3-1 b  
  2. 2012-3-2 a  
  3. 2012-3-3 b  
  4. 2012-3-4 d  
  5. 2012-3-5 a  
  6. 2012-3-6 c  
  7. 2012-3-7 d  
  8. 2012-3-3 c  

样例输出如下:

[plain]  view plain  copy
 
  1. 2012-3-1 a  
  2. 2012-3-1 b  
  3. 2012-3-2 a  
  4. 2012-3-2 b  
  5. 2012-3-3 b  
  6. 2012-3-3 c  
  7. 2012-3-4 d  
  8. 2012-3-5 a  
  9. 2012-3-6 b  
  10. 2012-3-6 c  
  11. 2012-3-7 c  
  12. 2012-3-7 d  


3.2 设计思路

数据去重的最终目标是让原始数据中出现次数超过一次的数据在输出文件中只出现一次。我们自然而然会想到将同一个数据的所有记录都交给一台 reduce 机器,无论这个数据出现多少次,只要在最终结果中输出一次就可以了。具体就是 reduce 的输入应该以数据作为 key,而对 value-list 则没有要求。当 reduce 接收到一个时就直接将 key复制到输出的 key 中,并将 value 设置成空值。

在 MapReduce 流程中, map 的输出经过 shuffle 过程聚集成后会交给 reduce。所以从设计好的 reduce 输入可以反推出 map 的输出 key 应为数据, value任意。继续反推, map 输出数据的 key 为数据,而在这个实例中每个数据代表输入文件中的一行内容,所以 map 阶段要完成的任务就是在采用 Hadoop 默认的作业输入方式之后,将value 设置为 key,并直接输出(输出中的 value 任意)。 map 中的结果经过 shuffle 过程之后交给 reduce。 reduce 阶段不会管每个 key 有多少个 value,它直接将输入的 key 复制为输出的 key,并输出就可以了(输出中的 value 被设置成空了)。

3.3 程序代码

DedupMapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.dedup;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.LongWritable;  
  6. import org.apache.hadoop.io.NullWritable;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Mapper;  
  9.   
  10. public class DedupMapper extends Mapper {  
  11.   
  12.     private static Text field = new Text();  
  13.   
  14.     @Override  
  15.     protected void map(LongWritable key, Text value, Context context)  
  16.             throws IOException, InterruptedException {  
  17.   
  18.         field = value;  
  19.         context.write(field, NullWritable.get());  
  20.   
  21.     }  
  22. }  


DedupReducer:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.dedup;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.NullWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Reducer;  
  8.   
  9. public class DedupReducer extends  
  10.         Reducer {  
  11.     @Override  
  12.     protected void reduce(Text key, Iterable values,  
  13.             Context context) throws IOException, InterruptedException {  
  14.   
  15.         context.write(key, NullWritable.get());  
  16.   
  17.     }  
  18. }  


DedupRunner:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.dedup;  
  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.NullWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  12.   
  13. public class DedupRunner {  
  14.     public static void main(String[] args) throws IOException,  
  15.             ClassNotFoundException, InterruptedException {  
  16.         Configuration conf = new Configuration();  
  17.         Job job = Job.getInstance(conf);  
  18.   
  19.         job.setJarByClass(DedupRunner.class);  
  20.   
  21.         job.setMapperClass(DedupMapper.class);  
  22.         job.setReducerClass(DedupReducer.class);  
  23.   
  24.         job.setOutputKeyClass(Text.class);  
  25.         job.setOutputValueClass(NullWritable.class);  
  26.   
  27.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  28.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  29.   
  30.         job.waitForCompletion(true);  
  31.     }  
  32. }  


打成jar包后运行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$  hadoop jar dedup.jar cn.nuc.hadoop.mapreduce.dedup.DedupRunner /user/exe_mapreduce/dedup/input /user/exe_mapreduce/dedup/out  


查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/dedup/output/part-r-00000  
  2. 2012-3-1 a  
  3. 2012-3-1 b  
  4. 2012-3-2 a  
  5. 2012-3-2 b  
  6. 2012-3-3 b  
  7. 2012-3-3 c  
  8. 2012-3-4 d  
  9. 2012-3-5 a  
  10. 2012-3-6 b  
  11. 2012-3-6 c  
  12. 2012-3-7 c  
  13. 2012-3-7 d  


4.  数据排序

“ 数据排序”是许多实际任务执行时要完成的第一项工作,比如学生成绩评比、数据建立索引等。这个实例和数据去重类似,都是先对原始数据进行初步处理,为进一步的数据 操作打好基础。下面进入这个示例。

4.1 实例描述

对输入文件中数据进行排序。 输入文件中的每行内容均为一个数字, 即一个数据。要求在输出中每行有两个间隔的数字,其中, 第一个代表原始数据在原始数据集中的位次, 第 二个代表原始数据。

样例输入:

file1:

[plain]  view plain  copy
 
  1. 2  
  2. 32  
  3. 654  
  4. 32  
  5. 15  
  6. 756  
  7. 65223  

file2:

[plain]  view plain  copy
 
  1. 5956  
  2. 22  
  3. 650  
  4. 92  

file3:

[plain]  view plain  copy
 
  1. 26  
  2. 54  
  3. 6  

样例输出:

[plain]  view plain  copy
 
  1. 1 2  
  2. 2 6  
  3. 3 15  
  4. 4 22  
  5. 5 26  
  6. 6 32  
  7. 7 32  
  8. 8 54  
  9. 9 92  
  10. 10 650  
  11. 11 654  
  12. 12 756  
  13. 13 5956  
  14. 14 65223  

4.2  设计思路

这个实例仅仅要求对输入数据进行排序,熟悉 MapReduce 过程的读者会很快想到在 MapReduce 过程中就有排序,是否可以利用这个默认的排序,而不需要自己再实现具体的 排序呢?答案是肯定的。

但是在使用之前首先需要了解它的默认排序规则。它是按照 key 值进行排序的,如果 key 为封装 int 的 IntWritable 类型,那么 MapReduce 按照数字大小对 key 排序,如果 key 为封装为 String 的 Text 类型,那么 MapReduce 按照字典顺序对字符串排序。

了解了这个细节,我们就知道应该使用封装 int 的 IntWritable 型数据结构了。也就是在 map 中将读入的数据转化成 IntWritable 型,然后作为 key 值输出( value 任意)。 reduce 拿到 之后,将输入的 key 作为 value 输出,并根据 value-list 中元素的个数决定 输出的次数。输出的 key(即代码中的 linenum)是一个全局变量,它统计当前 key 的位次。 需要注意的是这个程序中没有配置 Combiner,也就是在 MapReduce 过程中不使用 Combiner。 这主要是因为使用 map 和 reduce 就已经能够完成任务了。

4.3 程序代码

SortMapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.sort;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.IntWritable;  
  6. import org.apache.hadoop.io.LongWritable;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Mapper;  
  9.   
  10. //map将输入中的 value化成 IntWritable类型,作为输出的 key  
  11. public class SortMapper extends  
  12.         Mapper {  
  13.   
  14.     private static IntWritable data = new IntWritable();  
  15.     private static final IntWritable one = new IntWritable(1);  
  16.   
  17.     @Override  
  18.     protected void map(LongWritable key, Text value, Context context)  
  19.             throws IOException, InterruptedException {  
  20.         String line = value.toString();  
  21.         data.set(Integer.parseInt(line));  
  22.         context.write(data, one);  
  23.     }  
  24. }  

SortReducer:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.sort;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.IntWritable;  
  6. import org.apache.hadoop.mapreduce.Reducer;  
  7.   
  8. //reduce 将输入中的 key 复制到输出数据的 key 上,  
  9. //然后根据输入的 value‐list 中元素的个数决定 key 的输出次数  
  10. //用全局linenumber来代表key的位次  
  11. public class SortReducer extends  
  12.         Reducer {  
  13.   
  14.     private static IntWritable linenumber = new IntWritable(1);  
  15.   
  16.     @Override  
  17.     protected void reduce(IntWritable key, Iterable values,  
  18.             Context context) throws IOException, InterruptedException {  
  19.         for (IntWritable value : values) {  
  20.             context.write(linenumber, key);  
  21.             linenumber.set(linenumber.get() + 1);  
  22.             // linenumber=new IntWritable(linenumber.get()+1);  
  23.         }  
  24.   
  25.     }  
  26. }  

SotrRunner:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.sort;  
  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.IntWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  12.   
  13. public class SortRunner {  
  14.     public static void main(String[] args) throws IllegalArgumentException,  
  15.             IOException, ClassNotFoundException, InterruptedException {  
  16.         Configuration conf = new Configuration();  
  17.         Job job = Job.getInstance(conf);  
  18.   
  19.         job.setJarByClass(SortRunner.class);  
  20.   
  21.         job.setMapperClass(SortMapper.class);  
  22.         job.setReducerClass(SortReducer.class);  
  23.   
  24.         job.setOutputKeyClass(IntWritable.class);  
  25.         job.setOutputValueClass(IntWritable.class);  
  26.   
  27.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  28.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  29.   
  30.         job.waitForCompletion(true);  
  31.     }  
  32. }  


打成jar包运行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop jar sort.jar cn.nuc.hadoop.mapreduce.sort.SortRunner /user/exe_mapreduce/sort/input /user/exe_mapreduce/sort/output  

查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master input]$ hadoop fs -cat /user/exe_mapreduce/sort/output/part-r-00000  
  2. 1   2  
  3. 2   6  
  4. 3   15  
  5. 4   22  
  6. 5   26  
  7. 6   32  
  8. 7   32  
  9. 8   54  
  10. 9   92  
  11. 10  650  
  12. 11  654  
  13. 12  756  
  14. 13  5956  
  15. 14  65223  

5 平均成绩

“平均成绩”主要目的还是在重温经典“ WordCount”例子,可以说是在基础上的微变 化版,该实例主要就是实现一个计算学生平均成绩的例子。

5.1 实例描述

对输入文件中数据进行就算学生平均成绩。输入文件中的每行内容均为一个学生的姓名 和他相应的成绩,如果有多门学科,则每门学科为一个文件。要求在输出中每行有两个间隔 的数据,其中, 第一个代表学生的姓名, 第二个代表其平均成绩。

样本输入:

math:

[plain]  view plain  copy
 
  1. 张三 88  
  2. 李四 99  
  3. 王五 66  
  4. 赵六 77  

china:

[plain]  view plain  copy
 
  1. 张三 78  
  2. 李四 89  
  3. 王五 96  
  4. 赵六 67  

english:

[plain]  view plain  copy
 
  1. 张三 80  
  2. 李四 82  
  3. 王五 84  
  4. 赵六 86  

样本输出:

[plain]  view plain  copy
 
  1. 张三 82  
  2. 李四 90  
  3. 王五 82  
  4. 赵六 76  


5.2 设计思路

计算学生平均成绩是一个仿“ WordCount”例子,用来重温一下开发 MapReduce 程序的 流程。程序包括两部分的内容: Map 部分和 Reduce 部分,分别实现了 map 和 reduce 的功能。

Map 处理的是一个纯文本文件,文件中存放的数据时每一行表示一个学生的姓名和他 相应一科成绩。 Mapper 处理的数据是由 InputFormat 分解过的数据集,其中 InputFormat 的 作用是将数据集切割成小数据集 InputSplit,每一个 InputSlit 将由一个 Mapper 负责处理。此 外,InputFormat 中还提供了一个 RecordReader 的实现,并将一个 InputSplit 解析成对提供给了 map 函数。 InputFormat 的默认值是 TextInputFormat,它针对文本文件,按行将 文本切割成 InputSlit,并用 LineRecordReader 将 InputSplit 解析成对, key 是行在 文本中的位置, value 是文件中的一行。

Map 的结果会通过 partion 分发到 Reducer, Reducer 做完 Reduce 操作后,将通过以格 式 OutputFormat 输出。

Mapper 最终处理的结果对,会送到 Reducer 中进行合并,合并的时候,有 相同 key 的键/值对则送到同一个 Reducer 上。 Reducer 是所有用户定制 Reducer 类地基础, 它的输入是 key 和这个 key 对应的所有 value 的一个迭代器,同时还有 Reducer 的上下文。 Reduce 的结果由 Reducer.Context 的 write 方法输出到文件中。

5.3 程序代码

ScoreMapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.score;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.io.IntWritable;  
  7. import org.apache.hadoop.io.LongWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Mapper;  
  10.   
  11. public class ScoreMapper extends Mapper {  
  12.   
  13.     private static Text name = new Text();  
  14.     private static IntWritable score = new IntWritable();  
  15.   
  16.     @Override  
  17.     protected void map(LongWritable key, Text value, Context context)  
  18.             throws IOException, InterruptedException {  
  19.         String line = value.toString();  
  20.         String[] fields = StringUtils.split(line, " ");  
  21.         String strName = fields[0];//学生姓名  
  22.         int strScore = Integer.parseInt(fields[1]);//学生单科成绩  
  23.   
  24.         name.set(strName);  
  25.         score.set(strScore);  
  26.         context.write(name, score);  
  27.     }  
  28. }  

ScoreReducer:

[java]  view plain  copy
  1. package cn.nuc.hadoop.mapreduce.score;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.IntWritable;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Reducer;  
  8.   
  9. public class ScoreReducer extends Reducer {  
  10.   
  11.     private static IntWritable avg_score = new IntWritable();  
  12.   
  13.     @Override  
  14.     protected void reduce(Text key, Iterable values,  
  15.             Context context) throws IOException, InterruptedException {  
  16.         int sum_score = 0;//统计总成绩  
  17.         int count=0;//统计总的科目数  
  18.         for (IntWritable score : values) {  
  19.             count++;  
  20.             sum_score += score.get();         
  21.         }  
  22.   
  23.         avg_score.set(sum_score / count);  
  24.         context.write(key, avg_score);  
  25.     }  
  26. }  


ScoreRunner:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.score;  
  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.IntWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  12.   
  13. public class ScoreRunner {  
  14.     public static void main(String[] args) throws IOException,  
  15.             ClassNotFoundException, InterruptedException {  
  16.         Configuration conf = new Configuration();  
  17.         Job job = Job.getInstance(conf);  
  18.   
  19.         job.setJarByClass(ScoreRunner.class);  
  20.   
  21.         job.setMapperClass(ScoreMapper.class);  
  22.         job.setReducerClass(ScoreReducer.class);  
  23.   
  24.         job.setOutputKeyClass(Text.class);  
  25.         job.setOutputValueClass(IntWritable.class);  
  26.   
  27.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  28.         FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  29.   
  30.         job.waitForCompletion(true);  
  31.     }  
  32. }  


打成jar包执行:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop jar score.jar cn.nuc.hadoop.mapreduce.score.ScoreRunner /user/exe_mapreduce/score/input /user/exe_mapreduce/score/output  

查看结果:

[sql]  view plain  copy
 
  1. [hadoop@master ~]$ hadoop fs -cat /user/exe_mapreduce/score/output/part-r-00000  
  2. 张三  82  
  3. 李四  90  
  4. 王五  82  
  5. 赵六  76  

6 倒排索引

“ 倒排索引”是文档检索系统中最常用的数据结构,被广泛地应用于全文搜索引擎。 它主要是用来存储某个单词(或词组) 在一个文档或一组文档中的存储位置的映射,即提 供了一种根据内容来查找文档的方式。由于不是根据文档来确定文档所包含的内容,而是进 行相反的操作,因而称为倒排索引( Inverted Index)。

6.1 实例描述

通常情况下,倒排索引由一个单词(或词组)以及相关的文档列表组成,文档列表中的 文档或者是标识文档的 ID 号,或者是指文档所在位置的 URL,如图 6.1-1 所示。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第2张图片

从图 6.1-1 可以看出,单词 1 出现在{文档 1,文档 4,文档 13, ……}中,单词 2 出现 在{文档 3,文档 5,文档 15, ……}中,而单词 3 出现在{文档 1,文档 8,文档 20, ……} 中。在实际应用中, 还需要给每个文档添加一个权值,用来指出每个文档与搜索内容的相 关度,如图 6.1-2 所示。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第3张图片

最常用的是使用词频作为权重,即记录单词在文档中出现的次数。以英文为例,如图 6.1-3 所示,索引文件中的“ MapReduce”一行表示:“ MapReduce”这个单词在文本 T0 中 出现过 1 次,T1 中出现过 1 次,T2 中出现过 2 次。当搜索条件为“ MapReduce”、“ is”、“ Simple” 时,对应的集合为: {T0, T1, T2}∩{T0, T1}∩{T0, T1}={T0, T1},即文档 T0 和 T1 包 含了所要索引的单词,而且只有 T0 是连续的。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第4张图片

更复杂的权重还可能要记录单词在多少个文档中出现过,以实现 TF-IDF( Term Frequency-Inverse Document Frequency)算法,或者考虑单词在文档中的位置信息(单词是 否出现在标题中,反映了单词在文档中的重要性)等。

样例输入如下所示。

file1:

[plain]  view plain  copy
 
  1. MapReduce is simple  

file2:

[plain]  view plain  copy
 
  1. MapReduce is powerful is simple  

file3:

[plain]  view plain  copy
 
  1. Hello MapReduce bye MapReduce  

样例输出如下所示:

[plain]  view plain  copy
 
  1. MapReduce file1.txt:1;file2.txt:1;file3.txt:2;  
  2. is file1.txt:1;file2.txt:2;  
  3. simple file1.txt:1;file2.txt:1;  
  4. powerful file2.txt:1;  
  5. Hello file3.txt:1;  
  6. bye file3.txt:1;  

6.2 设计思路

实现“ 倒排索引”只要关注的信息为: 单词、 文档 URL 及词频,如图 3-11 所示。但是 在实现过程中,索引文件的格式与图 6.1-3 会略有所不同,以避免重写 OutPutFormat 类。下 面根据 MapReduce 的处理过程给出倒排索引的设计思路。

1)Map过程

首先使用默认的 TextInputFormat 类对输入文件进行处理,得到文本中每行的偏移量及 其内容。显然, Map 过程首先必须分析输入的对,得到倒排索引中需要的三个信 息:单词、文档 URL 和词频,如图 6.2-1 所示。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第5张图片

这里存在两个问题: 第一, 对只能有两个值,在不使用 Hadoop 自定义数据 类型的情况下,需要根据情况将其中两个值合并成一个值,作为 key 或 value 值; 第二,通 过一个 Reduce 过程无法同时完成词频统计和生成文档列表,所以必须增加一个 Combine 过程完成词频统计。

这里讲单词和 URL 组成 key 值(如“ MapReduce: file1.txt”),将词频作为 value,这样 做的好处是可以利用 MapReduce 框架自带的 Map 端排序,将同一文档的相同单词的词频组 成列表,传递给 Combine 过程,实现类似于 WordCount 的功能。

2)Combine过程

经过 map 方法处理后, Combine 过程将 key 值相同的 value 值累加,得到一个单词在文 档在文档中的词频,如图 6.2-2 所示。 如果直接将图 6.2-2 所示的输出作为 Reduce 过程的输 入,在 Shuffle 过程时将面临一个问题:所有具有相同单词的记录(由单词、 URL 和词频组 成) 应该交由同一个 Reducer 处理,但当前的 key 值无法保证这一点,所以必须修改 key 值 和 value 值。这次将单词作为 key 值, URL 和词频组成 value 值(如“ file1.txt: 1”)。这样 做的好处是可以利用 MapReduce 框架默认的 HashPartitioner 类完成 Shuffle 过程,将相同单 词的所有记录发送给同一个 Reducer 进行处理。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第6张图片

3)Reduce过程

经过上述两个过程后, Reduce 过程只需将相同 key 值的 value 值组合成倒排索引文件所 需的格式即可,剩下的事情就可以直接交给 MapReduce 框架进行处理了。如图 6.2-3 所示。 索引文件的内容除分隔符外与图 6.1-3 解释相同。

4)需要解决的问题

本实例设计的倒排索引在文件数目上没有限制,但是单词文件不宜过大(具体值与默 认 HDFS 块大小及相关配置有关),要保证每个文件对应一个 split。否则,由于 Reduce 过 程没有进一步统计词频,最终结果可能会出现词频未统计完全的单词。可以通过重写 InputFormat 类将每个文件为一个 split,避免上述情况。或者执行两次 MapReduce, 第一次 MapReduce 用于统计词频, 第二次 MapReduce 用于生成倒排索引。除此之外,还可以利用 复合键值对等实现包含更多信息的倒排索引。

Hadoop—MapReduce练习(数据去重、数据排序、平均成绩、倒排索引)_第7张图片

6.3 程序代码

InvertedIndexMapper:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.invertedindex;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.commons.lang.StringUtils;  
  6. import org.apache.hadoop.io.LongWritable;  
  7. import org.apache.hadoop.io.Text;  
  8. import org.apache.hadoop.mapreduce.Mapper;  
  9. import org.apache.hadoop.mapreduce.lib.input.FileSplit;  
  10.   
  11. public class InvertedIndexMapper extends Mapper {  
  12.   
  13.     private static Text keyInfo = new Text();// 存储单词和 URL 组合  
  14.     private static final Text valueInfo = new Text("1");// 存储词频,初始化为1  
  15.   
  16.     @Override  
  17.     protected void map(LongWritable key, Text value, Context context)  
  18.             throws IOException, InterruptedException {  
  19.   
  20.         String line = value.toString();  
  21.         String[] fields = StringUtils.split(line, " ");// 得到字段数组  
  22.   
  23.         FileSplit fileSplit = (FileSplit) context.getInputSplit();// 得到这行数据所在的文件切片  
  24.         String fileName = fileSplit.getPath().getName();// 根据文件切片得到文件名  
  25.   
  26.         for (String field : fields) {  
  27.             // key值由单词和URL组成,如“MapReduce:file1”  
  28.             keyInfo.set(field + ":" + fileName);  
  29.             context.write(keyInfo, valueInfo);  
  30.         }  
  31.     }  
  32. }  

InvertedIndexCombiner:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.invertedindex;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Reducer;  
  7.   
  8. public class InvertedIndexCombiner extends Reducer {  
  9.   
  10.     private static Text info = new Text();  
  11.   
  12.     // 输入:   
  13.     // 输出:  
  14.     @Override  
  15.     protected void reduce(Text key, Iterable values, Context context)  
  16.             throws IOException, InterruptedException {  
  17.         int sum = 0;// 统计词频  
  18.         for (Text value : values) {  
  19.             sum += Integer.parseInt(value.toString());  
  20.         }  
  21.   
  22.         int splitIndex = key.toString().indexOf(":");  
  23.         // 重新设置 value 值由 URL 和词频组成  
  24.         info.set(key.toString().substring(splitIndex + 1) + ":" + sum);  
  25.         // 重新设置 key 值为单词  
  26.         key.set(key.toString().substring(0, splitIndex));  
  27.           
  28.         context.write(key, info);  
  29.     }  
  30. }  

InvertedIndexReducer:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.invertedindex;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.io.Text;  
  6. import org.apache.hadoop.mapreduce.Reducer;  
  7.   
  8. public class InvertedIndexReducer extends Reducer {  
  9.   
  10.     private static Text result = new Text();  
  11.   
  12.     // 输入:  
  13.     // 输出:  
  14.     @Override  
  15.     protected void reduce(Text key, Iterable values, Context context)  
  16.             throws IOException, InterruptedException {  
  17.         // 生成文档列表  
  18.         String fileList = new String();  
  19.         for (Text value : values) {  
  20.             fileList += value.toString() + ";";  
  21.         }  
  22.   
  23.         result.set(fileList);  
  24.         context.write(key, result);  
  25.     }  
  26. }  

InvertedIndexRunner:

[java]  view plain  copy
 
  1. package cn.nuc.hadoop.mapreduce.invertedindex;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.hadoop.conf.Configuration;  
  6. import org.apache.hadoop.fs.FileSystem;  
  7. import org.apache.hadoop.fs.Path;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.Job;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  12.   
  13. public class InvertedIndexRunner {  
  14.     public static void main(String[] args) throws IOException,  
  15.             ClassNotFoundException, InterruptedException {  
  16.         Configuration conf = new Configuration();  
  17.         Job job = Job.getInstance(conf);  
  18.   
  19.         job.setJarByClass(InvertedIndexRunner.class);  
  20.   
  21.         job.setMapperClass(InvertedIndexMapper.class);  
  22.         job.setCombinerClass(InvertedIndexCombiner.class);  
  23.         job.setReducerClass(InvertedIndexReducer.class);  
  24.   
  25.         job.setOutputKeyClass(Text.class);  
  26.         job.setOutputValueClass(Text.class);  
  27.   
  28.         FileInputFormat.setInputPaths(job, new Path(args[0]));  
  29.         // 检查参数所指定的输出路径是否存在,若存在,先删除  
  30.         Path output = new Path(args[1]);  

你可能感兴趣的:(mapreduce)