MapReduce二次排序(secondary sort)实战

接触过mapreduce的同学都知道,为了将key值相同的record放在一起,分配给指定reducer,shuffle阶段会按照key值排序。
然而在某些情况下,我们需要同时对value排序,A同学立马提出了如下解决方案:reduce的时候,将同一个key的所有value都存在一个list中,最后再进行排序,这个方案在数据量小时没有问题,可是reducer的内存是有限的,当数据规模很大时,某个key可能会有几万个,几百万个value,上述方案在内存和性能上都是个灾难。
接着B同学灵机一动,既然map阶段按照key值排序,那么就把key和value一起作为新的key,使map输出,这样不就得到排序的结果了?但是B同学显然忘了shuffle好的数据是按照整个key来partition到各个reducer的,连接后的新key无法保证原key所有数据分发到同一个reducer上。
这时我们便需要mapreduce的二次排序机制了,二次排序机制其实和B同学的方案思路是相同的,只是在其之上增加了将key按照field进行partition的机制,指定分隔符,以及分隔之后按照第几个field做partition,这样就解决了原key不能partition到同一个reducer的问题。
举个简单的例子,我们要对如下的数据按照key聚合并排序:
a 1
b 3
b 1
a 3
a 2
b 2
我们期望得到的结果如下:
a 1
a 2
a 3
b 1
b 2
b 3
下面是使用python实现的hadoop streaming程序:
mapper:
import sys
for line in sys.stdin:
  key,val = line.strip().split(' ')
  print key + '|' + val + '\t' + '1'
reducer:
import sys
for line in sys.stdin:
  key,val = line.strip().split('\t')
  k1, v1 = key.split('|')
  print k1 + '\t' + v1
启动程序:
hadoop streaming \
  -D mapred.job.name="secondary_sort_demo" \
  -D stream.map.output.field.separator='\t'  \
  -D stream.num.map.output.key.fields=2 \
  -D map.output.key.field.separator='|' \
  -D num.key.fields.for.partition=1  \
  -partitioner 'org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner'  \
  -input ${input}                      \
  -output ${output}                    \
  -file ${mapper}                  \
  -file ${reducer}                   \
  -mapper ${mapper}                    \
  -reducer ${reducer}         
可以发现,二次排序体现在作业提交的参数中,partitioner指定key值的分隔方法,这个设置配合 map.output.key.field.separator和num.key.fields.for.partition一起使用,这里org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner按照 map.output.key.field.separator的值分隔整个key,对分割后的结果按照num.key.fields.for.partition指定的index的field进行partition,在本例中,mapper中把原key和value使用'|'连接为新key,使用'1'作为占位value,shuffle根据新key进行排序,partition根据设置按照原key进行,于是便得到了我们期望的结果。
本文用一个简单的例子讲述了mapreduce的二次排序使用方法,二次排序在mapreduce中有着各种各样的用途,例如多份数据做join等等,留给大家思考。

你可能感兴趣的:(Hadoop)