法一:增加单个task的内存使用量
增加最大 Heap值,即上图中 M2 的值,使每个 Task 可使用内存增加。
降低 Executor 的可用 Core 的数量 N , 使 Executor 中同时运行的任务数减少,在总资源不变的情况下,使每个 Task 获得的内存相对增加。当然,这会使得 Executor 的并行度下降。可以通过调高 spark.executor.instances 参数来申请更多的 executor 实例(或者通过
spark.dynamicAllocation.enabled 启动动态分配),提高job的总并行度。
法二:降低单个Task的内存消耗量
降低单个Task的内存消耗量可从配置方式和调整应用逻辑两个层面进行优化:
-
配置方式
减少每个 Task 处理的数据量,可降低 Task 的内存开销,在 Spark 中,每个 partition 对应一个处理任务 Task,因此,在数据总量一定的前提下,可以通过增加 partition 数量的方式来减少每个 Task 处理的数据量,从而降低 Task 的内存开销。针对不同的 Spark 应用类型,存在不同的 partition 配置参数 :
-
P = spark.default.parallism (非SQL应用)
-
P = spark.sql.shuffle.partition (SQL 应用)
通过增加 P 的值,可在一定程度上使 Task 现有内存满足任务运行。注: 当调整一个参数不能解决问题时,上述方案应进行协同调整。
又或者在代码中repartition设置分区数
法三:通过spark ui查看各个task的数据量(shuffle read 和shuffle write),看看是否出现数据倾斜
数据倾斜的解决方案
1、当某些key的数据量非常大时,可以在map阶段先把数据打乱(给key加上随机数前缀,比如0~10的随机前缀,就可以把一个key的数据分成十份放到不同分区中了),
然后再根据这种key 再shuffle随机到不同的分区。然后对这些加了前缀的key进行部分聚合,部分聚合完成后,再去除随机加的key的前缀,然后再把前面部分聚合的数据进行合并。
ps:这个原理是hive 中关于group by的一个优化参数hive.groupby.skewindata,原理和上述差不多。在尚硅谷的学习资料中知道的。原话如下:
当选项设定为 true,生成的查询计划会有两个 MR Job。
第一个 MR Job 中,Map 的输 出结果会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,
这样处理的结 果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的;
第 二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以 保证相同的 Group By Key 被分布到同一个 Reduce 中),
最后完成最终的聚合操作。
2、
可以把出现数据倾斜的那些key给先提前筛选出来,在代码中以硬编码的形式把这些数据过滤掉,这样当前任务就可以以低成本的方式处理这部分数据了。
(这样的话,这个任务在并行度不变的情况下,但每个executor的内存可以不用根据倾斜的那部分数据设置内存上限,使得内存利用率提高)
而那些被过滤掉的会导致数据倾斜的数据,再使用另一个任务专门进行处理。此时可以把executor的内存调高,但executor/core就不需要那么高了,因为只有少部分key,并行度要求不高。(或许这部分数据也可以参考1中的方式)
总结,我觉得处理数据倾斜的方式还是优先使用第1的方式,只不过代码多写几行而已。。可以提高内存利用率,并且后期不需要再麻烦的修改。第2的方式,硬编码是真的不推荐。