1. 前言
本文通过对MapReduce
的分析,列出
MapReduce
存在的问题,然后提出一种解决这些问题的改进型
MapReduce
,这种改进型的
MapReduce
暂且取名为
MapBalanceReduce
。由于经验和水平有限,所述观点和方法未必正确,诚心欢迎交流探讨。
2. 调度实体
在
MapReduce
和改进型
MapReduce
都存在
Job
和
Task
,它们之间的关系如下
UML
图所示:
![100906232341.png](http://blogimg.chinaunix.net/blog/upfile2/100906232341.png)
Job
是由一到多个
Tasks
组成的
Tasks
池,同一个
Job
内的各
Task
间是平等独立,不存在依赖也不存在优先级高低。
Job Tree
是在
MapReduce
之上的一层调度体,如存在于
Hadoop Hive
。
计算框架的作用就是通过将
Job
分解成
Tasks
,然后调配
Tasks
到集群中各节点去执行。因此
Tasks
是整个系统均衡和调度的核心对象。可以说,控制好了
Tasks
,就能够调度好均衡好,否则就可能发生数据倾斜,一些节点累死而另一些节点饿死。
3. MapReduce问题
MapReduce
最重要的基础是
DFS
(分布式文件系统),它的工作原理可简单的使用下图表示,包含了map
和
reduce
两个最核心的过程,以及
A
、
B
和
C
三个数据输入输出:
![100906232354.png](http://blogimg.chinaunix.net/blog/upfile2/100906232354.png)
通过分析A
、
B
和
C
不难得出如下表所示的特征:
A
|
B
|
C
| |
存储位置
|
DFS
|
本地存储
|
DFS
|
块大小是否均衡?
|
是
|
否
|
是
|
块大小是否可确定?
|
是
|
否
|
是
|
map
和
reduce
的块大小是否接近?
|
不确定,非受控
| ||
map
个数是否已知,非动态确定?
|
是
| ||
reduce
个数是否已知,非动态确定?
|
是
|
除了上表所述的特征外,
B
部分的数据块个数通常为预先指定的
reduce
个数,因此其值通常不大,而且将
reduce
个数增倍意义也不大。
3.1. map
由于
map
的输入源自于
DFS
,是相对静态的数据,所以各
MapTask
是均衡的,而且其大小是已知和确定的。
3.2. reduce
reduce
不同于
map
,它处理的是
map
后的数据,是动态产生的数据,只有在
map
完成之后才能确定的数据(包括数据的分布和大小等)。数据在经过
map
之后,就映射到了某个
ReducdeTask
,不能再更改,而且
ReduceTask
的个数也是不能修改的。
这会带来如下严重的问题:
1) reduce
端数据倾斜:比如有些
ReduceTask
需要处理
100GB
数据,而另有一些只需要处理
10GB
数据,甚至还有些
ReduceTask
可能空转,没有任何数据需要处理。最严重时,可能发生某个
ReduceTask
被分配了超出本地可用存储空间的数据量,或是超大数据量,需要特长处理时间;
2) reduce
端数据倾斜直接导致了
ReduceTask
不均衡;
3) 并行
Job
困难。类似于操作系统进程调度,如果要并行,必然存在
Job
间的调度切换,但由于
ReduceTask
需要处理的数据量可能很大,需要运行很长的时间,如果强制停止
ReduceTask
,对于大的
ReduceTask
会浪费大量的已运行时间,甚至可能导致一个大的
Job
运行失败。因此,无法实现类似于进程的并行调度器。
3.3. 数据不均衡的两种情况
数据不均衡可分为两类:
1) KEY
过于聚集,即不同
KEY
的个数虽多,但经过映射(如
HASH
)后,过于聚集在一起
采用两种办法相结合:一是将
KEY
分散得足够大(如
HASH
桶数够多),二是在
balance
的时候,进行重
HASH
,将大的打成小的。
2) KEY
值单一,即不同
KEY
的个数少
对于这种情况,采用
HASH
再分散的方法无效,事先也无法分散得足够大,但处理的方法也非常简单。对这种情况,按照大小进行横切即可,但这个时候一次
reduce
无法得到最终结果,至少需要连接两次
reduce
,另外还需要增加
balance
接口,以方便区别是最后一次
reduce
,还是中间的
reduce
。
3.4. 总结:两大主要问题
总的来说,
MapReduce
存在如下两大问题:
1) reduce
并非不均衡,可能导致严重的倾斜;
2) 并行调度能力弱,这是因为每个
Task
(主要是
ReduceTask
)的时间粒度不可控制。
4. 改进型MapReduce
4.1. 方案介绍
改进型
MapReduce
在
map
和
reduce
中间,增加一个
balance
过程,这个
balance
是可选的,只在必要时发生,如下图所示:
![100906232420.png](http://blogimg.chinaunix.net/blog/upfile2/100906232420.png)
如果map
输出的数据已经符合
reduce
均衡的要求,那么
balance
过程就什么也不做,否则它会在数据由
map
输出传递给
reduce
之前,做一次重新分配,重分配的目的是保证每个
reduce
的输入基本相同(允许有一定的差额),而且大小在指定的值上下浮动。
这个过程中,并不存在数据先由
map
传递给
balance
,再由
balance
传递给
reduce
,因此不会带来较大的额外开销。对于改进型
MapReduce
,其表现为:
X
|
Y
|
Z
|
W
| |
存储位置
|
DFS
|
本地存储
|
本地存储
|
DFS
|
块大小是否均衡?
|
是
|
否
|
是
|
是
|
块大小是否可确定?
|
是
|
否
|
是
|
是
|
map
和
reduce
的块大小是否接近?
|
是,有保证的
| |||
map
个数是否已知,非动态确定?
|
是
| |||
reduce
个数是否已知,非动态确定?
|
否,动态确定
|
map
输出时,将数据按指定的规则(如
Hash
),分成足够多的块(在
MapReduce
方案中为
reduce
个数),目的是方便在
balance
时,可以保证新的新块是均衡和大小在指定的范围内,所以
map
输出的块个数相对于
MapReduce
方案要多很多,通常为
10
倍以上,因为相对较小的块组合成指定大小的块简单高效些。
balance
发生在数据由
map
本地传输到
reduce
的过程中,它相当于一个路由器,在
map
的基础上,对数据进行再映射,这个过程的开销很小,因为不需要对数据进行计算,而只是确定块对应到哪个
reduce
。
map
输出的块,仍可能过大,这个时候需要
balance
对这部分数据进行拆分,可以将这些需要较长处理时间的工作,定义成一个新的
Task
,比如
BalanceTask
。
4.2. 并行调度
在所有
Task
均衡,且其大小是可控的前提下,并行调度就可以仿照进程调度去做。我们可以将
Task
当作一个运行时间片,由于其大小可以控制,所以只要大小适当,基本上就可以控制其运行时长。
当一个
Task
运行完后,根据调度规则来决定下一个运行的
Task
,下一个
Task
并不一定是同一个
Job
,和操作系统进程调度对比如下:
4.3. 新的不足
改进型的MapReducke
,由于在
reduce
之前需要一个
balance
过程,所以
reduce
和
map
两个过程在时间上没有重叠,因此对于单个
Job
,它的执行效率可能比
MapReduce
低。但一个
Job
的
map
可以和另一个
Job
的
reduce
在时间上重叠,因此并行多
Job
调度时,就不存在这样的不足了,而实际情况通常都是多
Job
并行调度,所以这个不足可以忽略。
4.4. 总结
改进型MapReduce
,实际上在
MapReduce
上做两件事:保证
ReduceTask
均衡和控制
ReduceTask
大小。