数据倾斜问题:数据处理与分析过程中的杀手

写在前面

本文面向的读者是从事数据分析、数据处理(ETL)等相关工作的朋友们,相信大家在工作中一定遇到过数据倾斜的问题,读完本文,你会了解到数据倾斜的定义及其危害、产生的原因及应对措施、常见倾斜场景及解决办法等知识,相信对你今后处理数据倾斜问题会有一定的帮助。

目前流行的大数据相关的计算框架之所以能够处理大量的数据和计算,基本上都是 依赖分布式计算 的思想,即由一个通过某种组织关系连接在一起的集群来共同完成计算任务。

这是一个非常好的计算模型,无论多大的数据量,只要集群可以扩展,就能够扩充算力,自如应对,但与此同时,也为数据倾斜的产生埋下了 伏笔

什么是数据倾斜

前面提到分布式计算,是一个集群共同承担计算任务,理想状态下,每个计算节点应该承担相近数据量的计算任务,然而实际情况通常不会这么理想, 数据分配严重不均就会产生数据倾斜 我们先来给数据倾斜下个明确点的定义。

数据倾斜,指的是并行处理的过程中,某些分区或节点处理的数据,显著高于其他分区或节点, 导致这部分的数据处理任务比其他任务要大很多 ,从而成为这个阶段执行最慢的部分,进而成为整个作业执行的瓶颈,甚至直接导致作业失败。

举个实际发生的例子说明下,一个spark作业,其中有个stage是由200个partition组成,在实际执行中,有198个partition在10秒内就完成了,但是有两个partition执行了3分钟都没有完成,并且在执行5分钟后失败了。 这便是典型的数据倾斜场景,通过观察SparkUI发现这两个partition要处理的数据是其他partition的30多倍,属于比较严重的数据倾斜。

数据倾斜的危害

知道了什么是数据倾斜,那么它到底有什么危害,让大家这么痛恨它的同时,又很畏惧它呢。

数据倾斜主要有三点危害:

1. 任务长时间挂起,资源利用率下降

计算作业通常是分阶段进行的,阶段与阶段之间通常存在数据上的依赖关系,也就是说后一阶段需要等前一阶段执行完才能开始。

举个例子 ,Stage1在Stage0之后执行,假如Stage1依赖Stage0产生的数据结果,那么Stage1必须等待Stage0执行完成后才能开始,如果这时Stage0因为数据倾斜问题,导致任务执行时长过长,或者直接挂起,那么Stage1将一直处于等待状态,整个作业也就一直挂起。 这个时候,资源被这个作业占据,但是却只有极少数task在执行,造成计算资源的严重浪费,利用率下降。

2.引发内存溢出,导致任务失败

数据发生倾斜时,可能导致大量数据集中在少数几个节点上,在计算执行中由于要处理的数据超出了单个节点的能力范围,最终导致内存被撑爆,报 OOM异常 ,直接导致任务失败。

3.作业执行时间超出预期,导致后续依赖数据结果的作业出错

有时候作业与作业之间,并没有构建强依赖关系,而是通过执行时间的前后时间差来调度,当前置作业未在预期时间范围内完成执行,那么当后续作业启动时便无法读取到其所需要的最新数据,从而导致连续出错。

可以看出,数据倾斜问题,就像是一个隐藏的杀手,潜伏在数据处理与分析的过程中,只要一出手,非死即伤。 那么它又是如何产生的呢? 想要解决它,我们就要先了解它。

为什么会产生数据倾斜

1.读入数据源时就是倾斜的

读入数据是计算任务的 开始 ,但是往往这个阶段就可能已经开始出现问题了。

对于一些本身就可能倾斜的数据源,在读入阶段就可能出现个别partition执行时长过长或直接失败,如读取id分布跨度较大的mysql数据、partition分配不均的kafka数据或不可分割的压缩文件。

这些场景下,数据在读取阶段或者读取后的第一个计算阶段,就会容易执行过慢或报错。

2.shuffle产生倾斜

在shuffle阶段造成倾斜,在实际的工作中更加常见,比如特定key值数量过多,导致join发生时,大量数据涌向一个节点,导致数据严重倾斜,个别节点的读写压力是其他节点的好几倍,容易引发OOM错误。

3.过滤导致倾斜

有些场景下,数据原本是均衡的,但是由于进行了一系列的数据剔除操作,可能在过滤掉大量数据后,造成数据的倾斜。

例如,大部分节点都被过滤掉了很多数据,只剩下少量数据,但是个别节点的数据被过滤掉的很少,保留着大部分的数据。 这种情况下,一般不会OOM,但是倾斜的数据可能会随着计算逐渐累积,最终引发问题。

怎么预防或解决数据倾斜

1.尽量保证数据源是均衡的

程序读入的数据源通常是上个阶段其他作业产生的,那么我们在上个阶段作业生成数据时,就要注意这个问题, 尽量不要给下游作业埋坑

如果所有作业都注意到并谨慎处理了这个问题,那么出现读入时倾斜的可能性会大大降低。

这个有个 小建议 ,在程序输出写文件时,尽量不要用coalesce,而是用repartition,这样写出的数据,各文件大小往往是均衡的。

2.对大数据集做过滤,结束后做repartition

对比较大的数据集做完过滤后,如果过滤掉了绝大部分数据,在进行下一步操作前,最好可以做一次repartition,让数据重回均匀分布的状态,否则失衡的数据集,在进行后续计算时,可能会逐渐累积倾斜的状态,容易产生错误。

3.对小表进行广播

如果两个数据量差异较大的表做join时,发生数据倾斜的常见解决方法,是将 小表广播到每个节点 去,这样就可以实现map端join,从而省掉shuffle,避免了大量数据在个别节点上的汇聚,执行效率也大大提升。

4.编码时要注意,不要人为造成倾斜

在写代码时,也要多加注意不要使用容易出问题的算子,如上文提到的coalesce。

另外,也要注意不要人为造成倾斜,如作者一次在帮别人排查倾斜问题时发现, 他在代码中使用开窗函数,其中写到over (partition by 1),这样就把所有数据分配到一个分区内,人为造成了倾斜

5.join前优化

个别场景下,两个表join,某些特殊key值可能很多,很容易产生数据倾斜,这时可以根据实际计算进行join前优化。

如计算是先join后根据key聚合,那可以改为先根据key聚合然后再join。 又如,需求是join后做distinct操作,在不影响结果的前提下,可以改为先distinct,然后再join。 这些措施都是可以有效 避免重复key过多导致join时倾斜

6.具体问题具体分析

某些具体问题或者解决方案,不具备普遍性,但是也可以作为一种思路参考。

例如,读入mysql数据时倾斜,这通常是由于mysql的id分布严重不均,中间存在跨度很大的区间造成的。 解决方法有两种,一是加大读取时的分区数,将倾斜的区间划分开; 另一种是,先把id取出来进行等宽切割,确保每个区段的id数量一致,之后再对各区间进行数据读取。

本文介绍了 什么是数据倾斜、它的危害、产生的原因及一些常用的解决方案 ,希望可以帮助大家,加深对数据倾斜的认识,如果遇到类似问题,可以快速上手解决掉。

-end-