MapReduce 经典案例手机流量排序的分析

在进行流量排序之前,先要明白排序是发生在map阶段,排序之后(排序结束后map阶段才会显示100%完成)才会到reduce阶段(事实上reduce也会排序),.此外排序之前要已经完成了手机流量的统计工作,即把第一次mr的结果作为本次排序的输入.也就是说读取的数据格式为     手机号 上行流量 下行流量 总流量

1,map阶段,读取并封装流量信息,不同的是context.write()时key必须是封装的实体类,而不再是手机号

 1 /**
 2  * 输入key 行号
 3  * 输入value 流量信息
 4  * 输出key 封装了流量信息的FlowBean
 5  * 输出value 手机号
 6  * @author tele
 7  *
 8  */
 9 public class FlowSortMapper extends Mapper<LongWritable,Text,FlowBean,Text>{
10     FlowBean flow  = new FlowBean();
11     Text v = new Text();
12     //读取的内容格式 手机号 上行流量 下行流量 总流量
13     @Override
14     protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowBean, Text>.Context context)
15             throws IOException, InterruptedException {
16         
17         //1.读取
18         String line = value.toString();
19         
20         //2.切割
21         String[] split = line.split("\t");
22         String upFlow = split[1];
23         String downFlow = split[2];
24         String phoneNum = split[0];
25         
26         //3.封装流量信息
27         flow.set(Long.parseLong(upFlow),Long.parseLong(downFlow));
28         
29         v.set(phoneNum);
30         
31         //4.写出
32         context.write(flow,v);
33         
34     }
35 }

2.map之后会根据key进行排序,因此如果要实现自定义排序,必须让定义的bean实现WritableComparable接口,并重写其中的compare方法,我们只需要告诉MapReduce根据什么排序,升序还是降序就可以了

具体的排序过程由MapReduce完成

 1 public class FlowBean implements WritableComparable<FlowBean>{
 2     private long upFlow;
 3     public long getUpFlow() {
 4         return upFlow;
 5     }
 6     public void setUpFlow(long upFlow) {
 7         this.upFlow = upFlow;
 8     }
 9     public long getDownFlow() {
10         return downFlow;
11     }
12     public void setDownFlow(long downFlow) {
13         this.downFlow = downFlow;
14     }
15     public long getSumFlow() {
16         return sumFlow;
17     }
18     public void setSumFlow(long sumFlow) {
19         this.sumFlow = sumFlow;
20     }
21     private long downFlow;
22     private long sumFlow;
23     
24     /**
25      * 反序列化时需要通过反射调用空参构造方法.必须有空参构造
26      */
27     public FlowBean() {
28         super();
29     }
30     
31     public FlowBean(long upFlow, long downFlow) {
32         super();
33         this.upFlow = upFlow;
34         this.downFlow = downFlow;
35         this.sumFlow = upFlow + downFlow;
36     }
37     
38     public void set(long upFlow, long downFlow) {
39         this.upFlow = upFlow;
40         this.downFlow = downFlow;
41         this.sumFlow = upFlow + downFlow;
42     }
43     
44     
45     /**
46      * 序列化与反序列化顺序必须一致
47      */
48 
49 
50     //序列化
51     @Override
52     public void write(DataOutput output) throws IOException {
53         output.writeLong(upFlow);
54         output.writeLong(downFlow);
55         output.writeLong(sumFlow);
56         
57     }
58     
59 
60     //反序列化
61     @Override
62     public void readFields(DataInput input) throws IOException {
63         upFlow = input.readLong();
64         downFlow = input.readLong();
65         sumFlow = input.readLong();
66     }
67     
68     /**
69      * reduce context.write()会调用此方法
70      */
71     @Override
72     public String toString() {
73         return upFlow + "\t" + downFlow + "\t" + sumFlow;
74     }
75     
76     
77     @Override
78     public int compareTo(FlowBean o) {
79         // -1表示不交换位置,即降序,1表示交换位置,升序
80         return this.sumFlow > o.getSumFlow() ? -1:1;
81     }
82     
83 }

3.reduce阶段,map阶段会对输出的value根据key进行分组,具有相同key的value会被划分到一组,这样reduce阶段执行一次reduce()读取一组,由于map阶段输出的key是定义的FlowBean,因此key是唯一的,从而

每组只有一个值,即Iterable<Text> value中只有一个值,也就是只有一个手机号

 1 /**
 2  * 输出的格式仍然为 手机号 上行流量 下行流量 总流量
 3  * @author tele
 4  *
 5  */
 6 public class FlowSortReducer extends Reducer<FlowBean,Text,Text,FlowBean>{
 7     /**
 8      * reduce阶段读入的仍然是一组排好序的数据
 9      * 前面map阶段输出的结果已根据key(FlowBean)进行分组,但由于此处key的唯一
10      * 所以一组只有一个数据,即 Iterable<Text> value 中只有一个值
11      */
12     @Override
13     protected void reduce(FlowBean key, Iterable<Text> value, Reducer<FlowBean, Text, Text, FlowBean>.Context context)
14             throws IOException, InterruptedException {
15         
16         //输出
17         Text phone = value.iterator().next();
18         context.write(phone,key);
19         
20         
21     }
22 }

下面进行debug,在map(),reduce()方法的开始与结束均打上断点,在FlowBean的compareTo()中也打上断点

map读取的内容

写出,注意key是FlowBean对象

接下来是排序,可以看到排序时map仍然不是100%,也就是说map阶段进行了排序(reduce阶段也会进行排序)

排序之后进入reduce阶段,reduce时write会调用FlowBean的toString()把结果输出到磁盘上

reduce除了归并排序之外,在执行write时同样会进行一次排序,执行第一组的write,(会调用FlowBean的toString()).但接下来还会去执行compareTo方法,此时在磁盘上生成的是临时目录,并且生成的part000文件是0KB,在执行完第二组的write之后才会真正把第一组数据写出到磁盘上

 

 

 

 part000此时有了数据

 

这样看来我们重写的compareTo方法无论在map阶段还是reduce阶段都被调用了

 

posted @ 2018-09-13 15:31  tele  阅读(1248)  评论(0编辑  收藏  举报