学会定制MapReduce里的partition,sort和grouping,Secondary Sort Made Easy

通过初期的几个开发员培训班,我发现有不少学员容易“偏爱”缺省的MapReduce行为,而忽略如何在代码里根据自己应用的需要来定制不同于系统缺省的行为。这篇文章结合Secondary Sort来介绍“Shuffle & Sort”里涉及到的三个重要操作。

缺省情况下,MapReduce Framework的Shuffle & Sort过程将所有和某一个键相关联的值“组合”(group)在一起,传送到一个唯一确定的Reducer,而且传送到每个Reducer的键是“排序”的(sort)。这对应到三个操作:1)“组合”; 2)“排序”; 和 3)partition(确定哪个键及其值的组合送到哪个Reducer)。

这三个操作涉及到最基本的MapRedcue工作原理,好理解。但是初学者容易忽略的地方是他们很容易记住这三个操作的缺省行为,却不清楚其实其中每一个的行为都是可以在代码里进行定制的。所以,下面首先介绍如何控制这三个操作行为:

- 定义partitioner来告诉MapReduce framework如何将见到的键和值送到哪个Reducer

- 定义key comparator来告诉MapReduce framework如何排序键

- 定义grouping comparator来告诉MapReduce framework如何控制“组合”键值在一起

这些很重要,它意味着MapReduce framework其实非常灵活,允许你根据应用来定制系统不同于缺省情况下的行为。这三个可以定制的行为在Secondary Sort里得到了应用和体现。下面我们介绍Secondary Sort。

用Hadoop做排序是一个常见的应用。其中,Secondary Sort则是一类特殊的排序问题,即:我们不仅要求key是排序的,而且要求对应相同键的值也是排序的。

譬如我们有一组原始数据:

a 1

z 3

b 2

a 100

a 3

b 1


如果只对键进行升序排序,编写并运行相应排序MapReduce,可能某次执行结果我们得到:

a 1

a 100

a 3

b 2

b 1

z 3


而另一次我们则得到不同的结果,可能为:

a 3

a 100

a 1

b 1

b 2

z 3


也就是说,结果是按键升序排序,但是对应相同的键,值不是排序的,并且不同运行得到的值得次序是不定的。如果我们需要对应相同的键,值按照数字升序排序,那么就是做一个secondary sort。对应上面的数据,预期的结果为:

a 1

a 3

a 100

b 1

b 2

z 3


实现Secondary Sort有些额外操作,其中某些步骤比较费解,因为要求不同于系统的缺省行为。下面结合输入输出的变化对如何实现Secondary Sort进行一些讲解。

首先,在Map端,我们需要产生一个复合键,其中一部分来自原始的“自然键”(譬如上面的a, b, z等),另一部分来自“自然键”对应的值。而Map的输出则是对应原始键-值对,产生相应的复合键-值对。

对应我们上面一开始给出的原始数据,中间产生的结果如下

a#1 1

z#3 3

b#2 2

a#100 100

a#3 3

b#1 1

注:这里为了讲解方便,我用“#”来连接原始的键-值对从而产生相应的复合键。在实际应用中,应该为其定义一个类。

接下来,我们在Map端还有三件事情需要做。

第一,需要保证我们期望的排序。这个在整个复合键上进行:首先按照复合建中的原始“自然键”部分进行排序;如果“自然键”相同,则按照复合键中的原始值部分进行排序。具体实现可以按照上述排序方法重载复合键类的compareTo方法;或者更好的办法是定义一个Comparator类按照上述排序方法重载compare方法,并在作业配置中指明使用该类:


conf.setOutputKeyComparatorClass(MyOKCC.class);


第二,我们需要定一个partitioner来保证相同“自然键”对应的记录都被送到同一个Reducer,并在作业配置中指明使用该类:


conf.setPartitionerClass(MyPartitioner.class);


第三,这个也是最费解的部分,有一些学员一开始都不是太理解,也是为什么我要写这篇文章的主要动因。通过Mapper产生复合键,以及上面两步,我们保证了相同自然键对应的记录都能到达同一个Reducer,并且按照我们所需要的方式排序。不过,费解的是,如果我们的Mapper产生如下这样的中间结果:

a#1   1

z#3   3

b#2   2

a#100 100

a#3   3

b#1   1


虽然 (a#1)   1, (a#3) 3,和(a#100)   100这三个记录能被送到同一个Reducer,可是它们的键并不相同,所以对应这三个仍然是分开的记录,而我们希望他们被“组合”在一起!

怎么办? MapReduce相当灵活,给我们提供了强大的机制来解决这个问题,这就涉及到我一开头说的控制“组合”操作。这个操作实际涉及到两方面:1)判定键相同;2)把相同键的值“组合”在一起。

正如我们一开始提到的,MapReduce的灵活性机制体现在这里,允许应用指定如何判定键相同。如果我们告诉MapReduce Framework只按照复合键的自然键部分进行判定,那么对于三个记录(a#1)   1, (a#3) 3,和(a#100)   100,在MapReduce的“眼里”,由于自然键部分都是“a”,那么他们是相同的。因而对应的三个值1, 3 和100将被“组合”放在一个列表里(1,3,100)作为对应(a#1)的值。也就是说,首先(a#1,1)被处理,接着系统看到(a#3,3),由于我们告诉系统这个键"a#3"和(a#1,1)的键"a#1"相同,系统将3作为和键"a#1"相关联的值来看待和进行“组合”;类似地,100也被作为和a#1相关联的值,等等。结果就是,传到Reducer的中间结果为(a#1, [1, 3, 100])。

这个判定键大小的部分通过以下来实现:首先实现一个Comparator类(譬如叫“MyOVGC”),只按照复合键中的自然键部分进行比较来重载compare方法;然后,在作业配置中指明使用该类:


conf.setOutputValueGroupingComparator(MyOVGC.class);


这样,Reducer收到的中间结果如下:

a#1,[1,3,100]

b#1,[1,2]

z#3,[3]


最后的事情就简单了,Reducer输出复合键里的原始自然键部分以及每一个值作为一个新的输出记录。由于键是排序的,而值也是排序的,最后的结果就是满足需要的结果。

下面我们把整个数据流列在下面以方便理解整个过程。


原始数据

a 1

z 3

b 2

a 100

a 3

b 1


Mapper产生的中间结果

a#1   1

z#3   3

b#2   2

a#100 100

a#3   3

b#1   1


只使用一个Reducer,看到的输入为

a#1 [1,3,100]

b#1 [1,2]

z#3 [3]


最后的输出为

a 1

a 3

a 100

b 1

b 2

z 3

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值