以手机流量统计案例举例分析:
原始文件格式:
手机号(phone): 上行流量(upFlow) 下行流量(downFlow) 总流量(sumFlow)
13865424521 100 100 200
13548796531 200 250 450
15387456365 200 300 500
18562465881 100 350 450
14265896452 100 350 450
13865424521 120 500 620
需求:
要求将相同的手机号的数据分为一组将相同手机号的三个流量相加 如上述数据的第一行数据和最后一行数据属于同一手机号 故应该将其合并输出
如下:
13865424521 220 600 820
自定义内容说明:
自定义javaBean: 将上述文件的phone upFlow downFlow封装
自定义排序规则:首先按照总流量升序排序 如果总流量一样就按照上行流量升序排序
自定义分组规则:手机号一样分为一个组
最终输出的数据:
手机号(phone): 上行流量(upFlow) 下行流量(downFlow) 总流量(sumFlow)
13865424521 100 100 200
18562465881 100 350 450
14265896452 100 350 450
13548796531 200 250 450
15387456365 200 300 500
13865424521 120 500 620
由输出的数据可见第一行和最后一行的相同手机号并没有分为一组 而是各自调用了一次reduce方法 故它们的流量没有加在一起 故自定义分组失效
失效过程演示:
首先明确一点在MapReduce框架中作为key的一定要实现排序才可以(至于为什么一定要有排序能力才行 见本文末尾补充内容),否则程序无法进行。本文中的key是自己定义的javaBean,那么就一定会实现WritableComparable接口重写里面的comparaTo方法实现自定义排序,再次重申如果javaBean没有实现自定义排序MapReduce程序是无法进行下去的。
redeceTask主动拉取自己分区的数据(如果有分区的话),此时拉到的数据是有序的(按照自定义的排序方法排序的)。数据如下:
<{13865424521,100,100,200} , NullWritable> <{18562465881,100,350,450} , NullWritable> <{14265896452,100,350,450} , NullWritable>
<{13548796531,200,250,450} , NullWritable> <{15387456365,200,300,500} , NullWritable> <{13865424521,120,500,620} , NullWritable>
最关键的点来了 reduceTask在进行分组过程是这样的 它是通过判断当前的数据和上一个数据是否是同一类数据 如果是那么它俩就放在一个组如果不是那么就认为前面的是一组的数据,然后会一起去调用一个redece方法。那么它是如何判断两个数据是否是同一类数据呢,这里分两种情况
1)没有自定义分组规则: 前面已经说了一定实现了排序规则 那么排序规则就会兼职分组规则 也就是说comparatTo方法返回0就代表这两个javaBean是同一组的。
2)有自定义分组规则: 那么就按照自定义的规则去判断是否是同一组的。
那么问题就来了 例如上面提到的数据第一条和最后一条根据自定义的分组规则应该是一组的 但是由于排序的缘故是他两被其他数据隔开了,那第一条数据和第二条数据不满足同一组规则 系统就将第一条单独作为一组去调用reduce方法了 而最后一条数据和它的前一条也不满足同一组规则所有最后一条单独调用了一个reduce方法。
总结:
redeceTask拉取到的有序数据的顺序是按照自定义排序得来的 只有连在一起并且满足分组规则的数据才会进入同一组
所有自定义分组字段要与排序字段保持一直。
补充:
1. 为什么作为key一定要有排序能力
因为根据reduceTask的分组规则要求reduceTask拉取到的数据必须是有序的 如果拉取到的数据不是有序的 那么reduceTask就要进行全局的寻找和它同一组的会导致效率极低 但是如果排了序只需要看看后面一个是否同组就可以了