Spark-JOIN场景下数据倾斜解决方案

4,312 阅读5分钟

背景

在使用Spark做数据处理的过程中,免不了需要多个数据集进行Join操作,例如数据撞库、字段维表补齐等,而此时正是数据倾斜常见的发生时刻。数据倾斜发生后,会严重干扰作业执行效率,甚至导致作业无法正常完成。

什么是数据倾斜?

数据倾斜是分布式数据处理中的一个极大影响作业效率的问题。通常是由数据分布不均,分布式系统中一个节点承载的数据量远高于其他节点承载的数据量,导致单节点处理速度慢或单节点失败,最终拖垮整个作业。

常见的Shuffle操作,例如Join、ReduceByKey、GroupByKey等都会潜在的导致数据倾斜的发生,本文以Join为例进行讲解。

image.png

JOIN类型

spark中 多个数据集Join方式也是有多种的。

此处不讨论LeftJoin、InnerJoin用法的区别,也不讨论HashJoin、SortMergeJoin等底层实现原理,而讨论Join的实现类型,更具体一点是讨论Join发生的位置

常见实现类型有2种,分别是 ReduceJoin、MapJoin,它们分别发生在程序的Reduce、Map部分。

Reduce Join

ReduceJoin是最常见的Join类型,发生在Reduce端(对于Spark任务讲Reduce端有些不恰当),通常是上游需要Join的两个表通过Map完成以后,并将数据shuffle写入磁盘or内存,下游Join任务(也就是Reduce端)拉取shuffle数据,并且进行Join操作。

image.png

ReduceJoin有以下两个风险点:

  1. shuffle is expensive,需要从Map端将数据Shuffle重新混洗,并且通过内存、磁盘、网络IO的形式发送给下游Reduce端,其中的硬件开销着实不小。
  2. 容易发生数据倾斜,因为shuffle混洗操作需要一个到多个字段来决定不同的记录会被分布到哪个节点,join操作时混洗分布的key默认为join的条件key

通过风险点的第二点可以知道,如果join条件对应的字段key出现了分布不均的情况,数据倾斜就很容易发生了,例如账号字段为空,为零值的情况,这时需要根据业务需求,判断是否丢弃这部分数据,或者使用其他处理方法。

Map Join

Map Join是一种Join的优化,发生在Map端,因此没有Shuffle操作,数据倾斜发生的可能性就较小

MapJoin的原理是将Join的两个表中小的一个,通过broadcast的方式加载到所有执行器的内存,然后再将另一个大表与执行器内存中的小表数据对比,因为避免了shuffle混洗,且全量数据基于内存计算,性能较好,且不易发生数据倾斜。

image.png 但MapJoin并不能在任意场景使用,其使用前提大致是这样的:

小表要足够小,小表过大会导致broadcast超时、执行器OutOfMemory异常

数据倾斜解决办法

发生了数据倾斜,解决办法有多种,其中要根据左右表数据集大小、集群计算资源、业务需求来看,我们假设集群计算资源不是卡点的情况下,有以下几种解决方案

小表Join大表

这种情况比较好解决,通过MapJoin就可以,通常来说可以在SQL中增加mapjoin暗示,

/*+ MAPJOIN(smalltable)*/

或者通过配置的方式开启mapjoin,增大mapjoin的表达小阈值,增大mapjoin对应broadcast行为的超时时间,来启用mapjoin,提升Join性能

set spark.sql.broadcastTimeout = 600

大表Join大表

大表JOIN大表比较复杂,意味着我们不能将左右表中任何一个通过MapJoin的方式加载到内存来解决。

大表Join大表遭遇数据倾斜时,通常都是表Join条件对应的字段出现了分布不均,可以通过SQL的形式观察一下key分布情况。

select 
    key, count(1) as cnt 
from 
    table 
group by key 

分为两种情况,大量无效key倾斜,大量有效key倾斜

大量无效key倾斜

这种情况通常是因为数据上报不规范导致的,例如用户行为流水表中的零值、空值、null值,这种数据可以提前过滤掉。

如果需求希望将带有零值的数据也保留下来,那么我们可以将零值数据通过随机数进行分布,在查询完毕之后再将这些随机数恢复成零值就可以了

-- 将零值数据直接干掉
select 
    key 
from table 
where length(key) > 0

-- 将零值数据均匀分布
select 
    if(length(key) = 0, md5(rand), key) as key
from table

-- 查询完成后 将零值数据恢复
select
    if (length(key) == 32, null, key) as key -- 因为md5固定32位,这里直接判断,也可以增加其他规则
from table

大量有效Key倾斜

大量有效Key倾斜的情况,例如用户流水中某个用户属于黑产产生了大量的刷子记录,或某个用户集中氪金大量,这种数据我们希望保留,但计算的过程中又会对作业产生影响,这时需要对key做进一步处理:

  1. 将产生倾斜的表,Key前增加一个随机的N位数字,例如随机0~20,key就变成了 随机数_key。这一步本质上是将有效key进一步打散,避免倾斜的问题。

  2. 将另一个表对应Join key 每条记录按第一步随机的N位数字个数,进行膨胀。目的是能够正常Join经过处理后倾斜表的数据,因此一个key会变成 N个key 例如 1_key、2_key、3_key、...、20_key。

  3. 将两个处理后的表进行Join,获取结果集后将前缀去掉,完成数据处理。