一 什么数据倾斜
琅琊榜spark作业的倾斜判断条件是:
Spark 任务数据倾斜:任意 Stage 1)至少存在一个时间倾斜度、数据量倾斜度均大于 2 的 Task,2)且倾斜 task 中最长执行时长与 Stage 执行时长的比值大于 0.5,3)且 Stage 的执行时长超过 1800 秒
什么是时间倾斜度和数据量倾斜度?
- 执行时间倾斜度定义为:所有并行节点执行时长的最大值 (Max) 与中位数 (Median) 的比值;
- 数据量倾斜度定义为:所有并行节点所分配的数据量的最大值 (Max) 与中位数 (Median) 的比值;
倾斜stage体现为:
这里数据倾斜需要和慢节点区分开来,慢节点也会看到某些task的执行时长很长,但是慢节点数据量倾斜度一般来说不满足数据倾斜的条件。而且慢节点的重试任务会执行的比较快;
二 场景和治理手段
场景 | 治理手段 |
---|---|
读取HIVE原始log日志表的时候出现了数据倾斜 | 大文件切割 |
在大表关联小表过程中出现了数据倾斜 | 使用广播关联 |
在两个大表JOIN过程中出现了数据倾斜 | 具体情况具体分析 |
1 读文件倾斜
执行引擎 | 解决办法 | 参数解释 |
---|---|---|
spark2 | spark.hadoop.hive.exec.orc.split.strategy=ETL | spark2的情况下读取ORC文件时由参数spark.hadoop.hive.exec.orc.split.strategy控制读取ORC表时生成split的策略。BI策略以文件为粒度进行split划分;ETL策略会将文件进行切分,多个stripe组成一个split;HYBRID策略为:当文件的平均大小大于hadoop最大split值(默认256 * 1024 * 1024)时使用ETL策略,否则使用BI策略。默认情况下使用的策略是HYBRID,但是有些情况下总体平均文件大小较小,但是个别文件的大小比较大时,使用HYBRID默认不会切分这些大文件。因此需要改成ETL策略。 |
spark3 | spark.sql.files.splitSingleFileByHiveStrategy=false | spark3提供了新的参数spark.sql.files.splitSingleFileByHiveStrategy,该参数默认值为true,即follow spark2的读取ORC策略。建议改成false,默认切割大文件。 |
2 大表关联小表
解决方案1:添加map join hint
解决方案2:添加参数 set spark.sql.statistics.fallBackToHdfs=true;
参数解释:当表的文件大小元数据信息不可能用时回退到hdfs计算表的文件大小,从而决定是否使用map join
3 大表关联大表
场景1:倾斜key为无效热key,如null、NA 等
解决办法:因此可以找表中离散程度较大的字段进行hash化,并拼接一些特殊字符串作为关联键。将关联条件从a.item_id = sku.sku_id改成case when length(a.item_id)>8 then a.item_id else concat(‘rand_’,abs(hash(a.event_timestamp))) end = sku.sku_id
场景2:倾斜key无固定值,可能为爬虫数据等等
解决办法1(维表扩容法):将B表的每一条记录通过复制成10条(视倾斜状况而定,可以提高倍数),分别标记成第0-9位次。在A表进行关联时,除了原有的关联键,还需额外生产一个可以随机生成10以内数字的关联键(找表中离散程度较大的字段进行hash化,并且摸10取余数即可),将和B表的位次进行关联
解决办法2(两段关联法): 首先生成一个将A表按照关联键进行group by取每个关联键的count值,得到count值较大的TOP记录临时表,这样我们就能确定倾斜的key了。将B表的内容划分两部分,一部分是B1包括确定倾斜的倾斜数据,一部分是B2包括非倾斜的数据。将A对B的关联改成 A和B1的广播关联,然后再和B2进行关联,在和B2关联的时候判断A的内容如果已经和B1关联上了,则将A的关联键进行打散,找表中离散程度较大的字段进行hash化,并拼接一些特殊字符串作为关联键
解决办法3(AQE):使用AQE
关于AQE的参数做下介绍:
参数 | 默认值 | 解释 |
---|---|---|
spark.sql.adaptive.enabled | false | 是否开启动态执行计划,如果需要开启AQE的倾斜处理功能,则这个参数必须为true |
spark.sql.adaptive.skewJoin.enabled | true | 是否开启动态调整倾斜功能 |
spark.sql.adaptive.skewJoin.skewedPartitionFactor | 5 | 只有当一个分区的大小 > 所有分区大小中位数乘以spark.sql.adaptive.skewJoin.skewedPartitionFactor时才认为是倾斜的分区 |
spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes | 256MB | 当一个分区的大小超过spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes大小时,才被认为是倾斜 |
spark.shuffle.accurateBlockThreshold | 100M | 在使用MapStatus的压缩时,shuffle block的大小超过该值时才会被准确记录,如果小于该值,Mapstatus的信息将会被一个平均值填充。即会影响到partition是否倾斜判断。注意只有没有开启RSS的作业才会用到这个参数 |
spark.shuffle.minNumPartitionsToHighlyCompress | 2000 | 当分区大小超过多少时,会使用MapStatus的压缩,即所有小于 spark.shuffle.accurateBlockThreshold(默认100M)的值都会被一个平均值所代替填充。即会影响到partition是否倾斜判断。注意只有没有开启RSS的作业才会用到这个参数 |
开启AQE:
如果使用AQE的话,将spark.sql.adaptive.enabled参数置为true,结合琅琊榜中判断数据倾斜度来看,建议设置spark.sql.adaptive.skewJoin.skewedPartitionFactor=2。另外如果作业属于非RSS作业时,并且shuffle partition分区的大小超过默认值2000时,记得调整spark.shuffle.minNumPartitionsToHighlyCompress的大小,并且适当增加driver的memory大小。
如何判断AQE已经生效:
可以查看在sparkUI上查看任务的DAG图,如果AQE已经成功处理数据倾斜则会在DAG的SortMergeJoin节点显示skew=true,并且在UI的物理执行计划树中显示倾斜的分区数和最大倾斜的分区、最小倾斜的分区的大小
不能使用AQE的场景:https://mp.weixin.qq.com/s/B3WFMZjOotpET90nfrE-GQ