一 概述
本文整理了笔者再从事大数据处理工作中对于数据倾斜问题的各类解决方案
二 现象
在hadoop,spark等分布式计算框架中运行某个作业时,发现某个算子(可能是map,也可能是reduce)执行特别慢
在hadoop中还不算特别致命,就是慢点,最终往往还是能算出结果,但是在spark等基于内存的计算框架中则很可能是致命的
往往会导致内存溢出或频繁的jvm full gc
三 发生点
一般发生在shuffle阶段,尤其是有join的场合,但是有些情况下在map端也会发生数据倾斜
四 产生原因
A 数据key值分布不均匀,少数key下包含的数据占比过大
例如医疗就诊数据中,往往是少数几个三甲医院产生了大部分的门急诊量,而大量的小医院就诊量确很少
股市中少数几支热门股占据了大部分交易量
两张表join时某些关联key,所关联的数据,占比过大
B 数据在计算节点间分布不均匀
类似hadoop,spark等框架都遵循一个原则,“移动计算,而不移动数据”,也就是作业调度算法会尽可能的
在实际存储数据的节点上调度执行计算任务,这样的好处是避免了传输大数据所带来的磁盘io和网络开销,
但正因为如此,如果某个节点数据非常多,那么在这个节点就会执行大量任务,这也是map端发生数据倾斜的原因
一般现在大数据存储都是在hdfs上,可执行hadoop 的balancer命令,使得数据均匀分布
五 解决方案
笔者从实施角度,按先易后难的顺序罗列一下自己所掌握的解决方案,一个有趣的现象是,往往容易的方案是更有效的方案
1 过滤掉导致倾斜的数据
这是最简单的方法,分析业务,当发现导致倾斜的数据是不需要的,直接抛弃这部分数据
最经典的例子是null值,源数据有大量key值是null的数据,这些null值在进行shuffle时会分配给同一个reduce任务处理
那么当业务允许的情况下,直接删掉这些数据
2 在数据源头尽量进行聚合
例如某个医院A有以下10条门急诊数据:
A 数据1
A 数据2
A 数据3
A 数据4
A 数据5
A 数据6
A 数据7
A 数据8
A 数据9
A 数据10
在正式进行统计分析任务前,进行数据预处理任务,使用聚合操作,把以上数据聚合成如下形式
A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10
3 提高并行度,也就是增加reduce任务数量,摊薄reduce任务的压力
这种方案,只能缓解数据倾斜,不是根本解决,效果要看具体场景,之所以放在第3条,是因为实在是很简单
4 打散倾斜key,进行多次聚合
如果在数据源没有进行聚合,或者无法聚合,或者聚合后还是有倾斜key存在,则可以使用此方案
还是以上述A医院的数据为例,我们使用一个随机数,将A随机变换成A-1,....A-N形式,假设随机数取值为1至3
则以上数据会变成如下形式
A-1 数据1
A-2 数据2
A-3 数据3
A-1 数据4
A-2 数据5
A-3 数据6
A-1 数据7
A-2 数据8
A-3 数据9
A-1 数据10
针对这种形式的数据,我们进行两次聚合,第一次聚合结果如下
A-1 数据1,数据4,数据7,数据10
A-2 数据2,数据5,数据8
A-3 数据3,数据6,数据9
第二次聚合,去掉随机数再聚合,结果如下
A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10
本方案的局限性在于,不适用于join产生的数据倾斜
5 对于join产生的数据倾斜,优先考虑将reduce端的join,转换为map端的join
因为map端是一条条数据去join的,不存在归并的过程,也就自然没有了数据倾斜
首先讨论一下,为什么join操作,常规是用reduce算子来做
原因很简单,map reduce思想是分而治之的思想,map负责分,reduce负责合
同一套数据在map过程中会分给Nmap任务,最终按关联关系,将相同的key所对应的数据输出给同一个reduce任务
要做join则必须拿到join两端的所有数据,这只有在reduce的过程中才能拿到
从上述分析可见,map端做不了join是由于无法获得join两端的所有必须数据,那么如果我们提供一种方法
让map端能获得join两端的所有必须数据,则map端就能进行join了。好在框架已经为我们提供了这种方法。
在hadoop中可以用DistributedCache.addCacheFile
在spark中可以用broadcast广播变量
在hive中可以用/*+ MAPJOIN() */
以上代码的作用都是将join两端中的某一端(一般是数据量小的一端),广播出来,分发到所有运行map任务的节点
这样map端就可以执行join了
本方案的局限性在于,需要将join的一端,完全载入map任务节点的内存中,因此只适用于join的一端是小表的情况,
不适用于join两端都是大表的情况,不过对于大表join大表的场景,笔者曾经尝试在hadoop mapreduce程序中将大表预先
加载到redis等高并发缓存中,也取得不错的效果(这种方式其实是用增大网络流量的方式来换取节约内存空间,对网络传输速度有较高要求)
6 抽取分割倾斜数据,分别进行join后再归并
对于无法用map join方案的可尝试此方案
原理
预先分析源数据,将会发生数据倾斜的数据,单独拉出来;
用这个会倾斜的数据单独去join一下,这个时候,key对应的数据,可能就会分散到多个任务中去进行join操作。
例
数据表 A 包含倾斜key,内容为
key value
1 x1
1 x2
1 x3
1 x4
1 x5
1 x6
2 x7
3 x8
数据表 B 为要join的表,内容为
key value
1 y1
2 y2
3 y3
首先,分析数据表 A的倾斜key,拆分 A 为 A1(只含倾斜key) A2(不含倾斜key)
A1 内容为
key value
1 x1
1 x2
1 x3
1 x4
1 x5
1 x6
A2 内容为
key value
2 x7
3 x8
然后,对数据表 B也按照倾斜key 拆分出 B1(只含倾斜key)
B1 内容为
key value
1 y1
对A1,对key打上随机数前缀 得到 AP,这里的随机数取值范围为(1,2,3)
AP 内容为
key value
1-1 x1
2-1 x2
3-1 x3
1-1 x4
2-1 x5
3-1 x6
对B1,对key打上有序唯一数前缀 得到BP,这里有序唯一数取值范围必须覆盖上面的随机数范围(1,2,3)
BP 内容为
key value
1-1 y1
2-1 y1
3-1 y1
AP join BP 得到 ABP 这里可以结合提升reduce操作并行度获得更好的效果
ABP 内容为
key value
1-1 x1,y1
1-1 x4,y1
2-1 x2,y1
2-1 x5,y1
3-1 x3,y1
3-1 x6,y1
对ABP 去掉随机数前缀 得到 AB1
AB1 内容为
key value
1 x1,y1
1 x4,y1
1 x2,y1
1 x5,y1
1 x3,y1
1 x6,y1
用A2 join B 得到 AB2
AB2 内容为
key value
2 x7,y2
3 x8,y3
最终合并AB1 和AB2
key value
1 x1,y1
1 x4,y1
1 x2,y1
1 x5,y1
1 x3,y1
1 x6,y1
2 x7,y2
3 x8,y3
本方案适用于源数据中倾斜key只有少数几个的场景
7 随机扩容
对于join数据中有很多倾斜key,不能用方案6解决的,可以尝试本方案
选择一个join表,进行扩容,将每条数据,用有序唯一数作为前缀打散,映射为多条数据,通常选择10以内的有序唯一数,即最多扩容10倍。
将另外一个表,做普通的map映射操作,每条数据,都打上一个10以内的随机数前缀。
最后,将两个处理后的表,进行join操作
本方案只是对数据倾斜的缓解,而不是根本解决,可以看出本方案的本质是通过放大数据量来谋求数据的均衡,代价还是比较明显的,
因此只有前述方案都无法适用时,才应考虑使用本方案
本文整理了笔者再从事大数据处理工作中对于数据倾斜问题的各类解决方案
二 现象
在hadoop,spark等分布式计算框架中运行某个作业时,发现某个算子(可能是map,也可能是reduce)执行特别慢
在hadoop中还不算特别致命,就是慢点,最终往往还是能算出结果,但是在spark等基于内存的计算框架中则很可能是致命的
往往会导致内存溢出或频繁的jvm full gc
三 发生点
一般发生在shuffle阶段,尤其是有join的场合,但是有些情况下在map端也会发生数据倾斜
四 产生原因
A 数据key值分布不均匀,少数key下包含的数据占比过大
例如医疗就诊数据中,往往是少数几个三甲医院产生了大部分的门急诊量,而大量的小医院就诊量确很少
股市中少数几支热门股占据了大部分交易量
两张表join时某些关联key,所关联的数据,占比过大
B 数据在计算节点间分布不均匀
类似hadoop,spark等框架都遵循一个原则,“移动计算,而不移动数据”,也就是作业调度算法会尽可能的
在实际存储数据的节点上调度执行计算任务,这样的好处是避免了传输大数据所带来的磁盘io和网络开销,
但正因为如此,如果某个节点数据非常多,那么在这个节点就会执行大量任务,这也是map端发生数据倾斜的原因
一般现在大数据存储都是在hdfs上,可执行hadoop 的balancer命令,使得数据均匀分布
五 解决方案
笔者从实施角度,按先易后难的顺序罗列一下自己所掌握的解决方案,一个有趣的现象是,往往容易的方案是更有效的方案
1 过滤掉导致倾斜的数据
这是最简单的方法,分析业务,当发现导致倾斜的数据是不需要的,直接抛弃这部分数据
最经典的例子是null值,源数据有大量key值是null的数据,这些null值在进行shuffle时会分配给同一个reduce任务处理
那么当业务允许的情况下,直接删掉这些数据
2 在数据源头尽量进行聚合
例如某个医院A有以下10条门急诊数据:
A 数据1
A 数据2
A 数据3
A 数据4
A 数据5
A 数据6
A 数据7
A 数据8
A 数据9
A 数据10
在正式进行统计分析任务前,进行数据预处理任务,使用聚合操作,把以上数据聚合成如下形式
A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10
3 提高并行度,也就是增加reduce任务数量,摊薄reduce任务的压力
这种方案,只能缓解数据倾斜,不是根本解决,效果要看具体场景,之所以放在第3条,是因为实在是很简单
4 打散倾斜key,进行多次聚合
如果在数据源没有进行聚合,或者无法聚合,或者聚合后还是有倾斜key存在,则可以使用此方案
还是以上述A医院的数据为例,我们使用一个随机数,将A随机变换成A-1,....A-N形式,假设随机数取值为1至3
则以上数据会变成如下形式
A-1 数据1
A-2 数据2
A-3 数据3
A-1 数据4
A-2 数据5
A-3 数据6
A-1 数据7
A-2 数据8
A-3 数据9
A-1 数据10
针对这种形式的数据,我们进行两次聚合,第一次聚合结果如下
A-1 数据1,数据4,数据7,数据10
A-2 数据2,数据5,数据8
A-3 数据3,数据6,数据9
第二次聚合,去掉随机数再聚合,结果如下
A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10
本方案的局限性在于,不适用于join产生的数据倾斜
5 对于join产生的数据倾斜,优先考虑将reduce端的join,转换为map端的join
因为map端是一条条数据去join的,不存在归并的过程,也就自然没有了数据倾斜
首先讨论一下,为什么join操作,常规是用reduce算子来做
原因很简单,map reduce思想是分而治之的思想,map负责分,reduce负责合
同一套数据在map过程中会分给Nmap任务,最终按关联关系,将相同的key所对应的数据输出给同一个reduce任务
要做join则必须拿到join两端的所有数据,这只有在reduce的过程中才能拿到
从上述分析可见,map端做不了join是由于无法获得join两端的所有必须数据,那么如果我们提供一种方法
让map端能获得join两端的所有必须数据,则map端就能进行join了。好在框架已经为我们提供了这种方法。
在hadoop中可以用DistributedCache.addCacheFile
在spark中可以用broadcast广播变量
在hive中可以用/*+ MAPJOIN() */
以上代码的作用都是将join两端中的某一端(一般是数据量小的一端),广播出来,分发到所有运行map任务的节点
这样map端就可以执行join了
本方案的局限性在于,需要将join的一端,完全载入map任务节点的内存中,因此只适用于join的一端是小表的情况,
不适用于join两端都是大表的情况,不过对于大表join大表的场景,笔者曾经尝试在hadoop mapreduce程序中将大表预先
加载到redis等高并发缓存中,也取得不错的效果(这种方式其实是用增大网络流量的方式来换取节约内存空间,对网络传输速度有较高要求)
6 抽取分割倾斜数据,分别进行join后再归并
对于无法用map join方案的可尝试此方案
原理
预先分析源数据,将会发生数据倾斜的数据,单独拉出来;
用这个会倾斜的数据单独去join一下,这个时候,key对应的数据,可能就会分散到多个任务中去进行join操作。
例
数据表 A 包含倾斜key,内容为
key value
1 x1
1 x2
1 x3
1 x4
1 x5
1 x6
2 x7
3 x8
数据表 B 为要join的表,内容为
key value
1 y1
2 y2
3 y3
首先,分析数据表 A的倾斜key,拆分 A 为 A1(只含倾斜key) A2(不含倾斜key)
A1 内容为
key value
1 x1
1 x2
1 x3
1 x4
1 x5
1 x6
A2 内容为
key value
2 x7
3 x8
然后,对数据表 B也按照倾斜key 拆分出 B1(只含倾斜key)
B1 内容为
key value
1 y1
对A1,对key打上随机数前缀 得到 AP,这里的随机数取值范围为(1,2,3)
AP 内容为
key value
1-1 x1
2-1 x2
3-1 x3
1-1 x4
2-1 x5
3-1 x6
对B1,对key打上有序唯一数前缀 得到BP,这里有序唯一数取值范围必须覆盖上面的随机数范围(1,2,3)
BP 内容为
key value
1-1 y1
2-1 y1
3-1 y1
AP join BP 得到 ABP 这里可以结合提升reduce操作并行度获得更好的效果
ABP 内容为
key value
1-1 x1,y1
1-1 x4,y1
2-1 x2,y1
2-1 x5,y1
3-1 x3,y1
3-1 x6,y1
对ABP 去掉随机数前缀 得到 AB1
AB1 内容为
key value
1 x1,y1
1 x4,y1
1 x2,y1
1 x5,y1
1 x3,y1
1 x6,y1
用A2 join B 得到 AB2
AB2 内容为
key value
2 x7,y2
3 x8,y3
最终合并AB1 和AB2
key value
1 x1,y1
1 x4,y1
1 x2,y1
1 x5,y1
1 x3,y1
1 x6,y1
2 x7,y2
3 x8,y3
本方案适用于源数据中倾斜key只有少数几个的场景
7 随机扩容
对于join数据中有很多倾斜key,不能用方案6解决的,可以尝试本方案
选择一个join表,进行扩容,将每条数据,用有序唯一数作为前缀打散,映射为多条数据,通常选择10以内的有序唯一数,即最多扩容10倍。
将另外一个表,做普通的map映射操作,每条数据,都打上一个10以内的随机数前缀。
最后,将两个处理后的表,进行join操作
本方案只是对数据倾斜的缓解,而不是根本解决,可以看出本方案的本质是通过放大数据量来谋求数据的均衡,代价还是比较明显的,
因此只有前述方案都无法适用时,才应考虑使用本方案