《Hadoop实战》第五章:MapReduce 应用案例
1.单词计数设计思路
这个应用实例的解决方案很直接,就是将文件内容切分成单词,然后将所有相同的单词聚集在一起,最后计算单词出现的次数并输出。根据MapReduce并行程序设计原则可知,解决方案中的内容切分步骤和数据不相关,可以并行化处理,每个获得原始数据的机器只要将输人数据切分成单词就可以了。所以可以在Map阶段完成单词切分任务。另外, 相同单词的频数计算也可以并行化处理。由实例要求来看,不同单词之间的频数不相关,所以可以将相同的单词交给一台机器来计算频数,然后输出最终结果。这个过程可以在Reduce阶段完成。至于将中间结果根据不同单词分组再分发给Reduce机器,这正好是MapReduce过程中的shuffle能够完成的。至此,这个实例的MapReduce程序就设计出来了。Map阶段完成由输入数据到单词切分的工作,shuffle阶段完成相同单词的聚集和分发工作(这个过程 是MapReduce的默认过程,不用具体配置),Reduce 阶段负责接收所有单词并计算其频数。MapReduce中传递的数据都是<key,value>形式的,并且shuffle排序聚集分发都是按照key值进行的,因此将Map的输出设计成由word作为key.1作为value的形式,这表示单词word出现了一次(Map 的输入采用Hadoop默认的输人方式:文件的一行作为value,行号作为key)。Reduce的输人为Map输出聚集后的结果,即<key,value-list>,具体到这个实例就是<word,{1,1,1-.}>, Reduce 的输出会设计成与Map输出相同的形式, 只是后面的数字不再固定是1,而是具体算出的word所对应的频数。
2.数据去重设计思路
数据去重实例的最终目标是让原始数据中出现次数超过一次的数据在输出文件中只出现一次。我们自然而然会想到将同一个数据的所有记录都交给-台Reduce机器,无论这个数据出现多少次,只要在最终结果中输出一次就可以了。具体就是Reduce的输入应该以数据作为key,而对value-list则没有要求。当Reduce接收到一个<key,value-list> 时就直接将key复制到输出的key中,并将value设置成空值。在MapReduce流程中,Map的输出<key,value>经过shuffle过程聚集成<key,value-list> 后会被交给Reduce。所以从设计好的Reduce输入可以反推出Map输出的key应为数据,而value为任意值。继续反推,Map输出的key为数据。而在这个实例中每个数据代表输人文件中的一行内容,所以Map阶段要完成的任务就是在采用Hadoop默认的作业输人方式之后,将value设置成key,并直接输出(输出中的value任意)。Map中的结果经过shuffle过程之后被交给Reduce.在Reduce阶段不管每个key有多少个value,都直接将输人的key复制为输出的key,并输出就可以了(输出中的value被设置成空)。
3.排序设计思路
这个实例仅仅要求对输入数据进行排序,熟悉MapReduce过程的读者很快会想到在MapReduce过程中就有排序。是否可以利用这个默认的排序、而不需要自己再实现具体的排序呢?答案是肯定的。但是在使用之前首先要了解MapReduce过程中的默认排序规则。它是按照key值进行排序,如果key为封装int的IntWritable 类型, 那么MapReduce按照数字大小对key排序;如果key为封装String的Text类型,那么MapReduce按照字典顺序对字符串排序。需要注意的是,Reduce 自动排序的数据仅仅是发送到自己所在节点的数据,使用默认的排序并不能保证全局的顺序,因为在排序前还有一个partition的过程,默认无法保证分割后各个Reduce上的数据整体是有序的。所有要想使用默认的排序过程,还必须定义自己的Partition类,保证执行Partition过程之后所有Reduce.上的数据在整体上是有序的,然后再对局部Reduce上的数据进行默认排序,这样才能保证所有数据有序。了解了这个细节,我们就知道,首先应该使用封装int的IntWritable型数据结构, 也就是将读入的数据在Map中转化成IntWritable型,然后作为key值输出(value 任意);其次需要重写partition类,保证整体有序,具体做法是用输入数据的最大值除以系统partition数量的商作为分割数据的边界增量,也就是说分割数据的边界为此商的1倍、2倍至numPartitions-1倍,这样就能保证执行partition后的数据是整体有序的;然后Reduce获得<key,value-list> 之后,根据value-list中元素的个数将输入的key作为value的输出次数,输出的key是一个全局变量,用于统计当前key的位次。需要注意的是,这个程序中没有配置Combiner, 也就是说在MapReduce过程中不使用Combiner.这主要是因为使用Map和Reduce就已经能够完成任务了。
4.单表关联设计思路
#uc# 分析这个实例,显然需要进行单表连接,连接的是左表的parent列和右表的child列,且左表和右表是同一个表。连接结果中除去连接的两列就是所需要的结果一grandchild-grandparent表。要用MapReduce实现这个实例,首先要考虑如何实现表的自连接,其次就是连接列的设置,最后是结果的整理。考虑到MapReduce的shuffle过程会将相同的key值放在一起,所以可以将Map结果的key值设置成待连接的列,然后列中相同的值自然就会连接在一起了。再与最开始的分析联系起来:要连接的是左表的parent列和右表的child列,且左表和右表是同一个表,所以在Map阶段将读人数据分割成child和parent之后,会将parent设置为key, child 设置为value进行输出,作为左表;再将同一对child和parent中的child设置成key, parent设置成value进行输出,作为右表。为了区分输出中的左右表,需要在输出的value中再加上左右表信息,比如在value的String最开始处加上字符1表示左表、字符2表示右表。这样在Map的结果中就形成了左表和右表,然后在shuffle过程中完成连接。在Reduce接收到的连接结果中,每个key的value-list就包含了grandchild和grandparent关系。取出每个key的value-list进行解析,将左表中的child放人一一个数组,右表中的parent放入一个数组,然后对两个数组求笛卡儿积就是最后的结果了。
5.多表关联设计思路 多表关联和单表关联相似,都类似于数据库中的自然连接。相比单表关联,多表关联的左右表和连接列更加清楚, 因此可以采用和单表关联相同的处理方式。Map 识别出输人的行属于哪个表之后,对其进行分割,将连接的列值保存在key中,另一列和左右表标志保存在value中,然后输出。Reduce拿到连接结果后,解析value内容,根据标志将左右表内容分开存放,然后求笛卡儿积,最后直接输出。