场景
执行一个spark任务后,发现控制台一直报错如下:
21/04/21 10:32:29 ERROR cluster.YarnScheduler: Lost executor 3 on cdh-slave1.test.com: Container killed by YARN for exceeding memory limits. 5.5 GB of 5.5 GB physical memory used. Consider boosting spark.yarn.executor.memoryOverhead or disabling yarn.nodemanager.vmem-check-enabled because of YARN-4714.
21/04/21 10:32:29 ERROR client.TransportResponseHandler: Still have 1 requests outstanding when connection from /172.16.4.156:51552 is closed
21/04/21 10:32:31 ERROR cluster.YarnScheduler: Lost executor 2 on cdh-slave3.test.com: Container killed by YARN for exceeding memory limits. 5.5 GB of 5.5 GB physical memory used. Consider boosting spark.yarn.executor.memoryOverhead or disabling yarn.nodemanager.vmem-check-enabled because of YARN-4714.
执行脚本如下:
第一反应内存不够了,于是加executor内存:
--executor-memory 6g
重新执行后报错:
报错不一样,这次是单个executor因为超过yarn容器限制导致创建失败
查看yarn配置:
单个容器内存最大6G,总所周知:spark任务单个executor所需内存计算规则:
executorMemory + MAX(executorMemory * 0.10, 384m)
所以单个executor需要6144M+614M的内存,所以单个executor最大只能配置5G内存。
由于是测试环境,内存有限,yarn总共最大24G资源,且还有其他小伙伴在用集群,硬件限制的死死的。
于是先去找领导申请资源,得到的回复是先做优化,正式环境到时候可以适当的加内存。
中国有句古话说的好:在绝对的大内存面前,任何的代码优化都显得花里胡哨。
那只有整点花里胡哨的了,在有限的资源上做优化,目标12G到16G内存之间能支棱起来,时间也不能慢。
又回到原点,修改executor内存5G,还是报最原始的错误。
于是发现事情并不简单。需要仔细排查一下了。
报错还很贴心的提示了让我扩大spark.yarn.executor.memoryOverhead或者禁用yarn虚拟内存检测。
扩大spark.yarn.executor.memoryOverhead
解决问题,这次正常
性能优化
数据:输入2500W的ADB数据,输出1500WADB数据
硬件:12核,20G内存
当前程序跑完时间:2h
领导说时间太长了,要优化
那就需要研读代码了(我也只是个接盘侠)
查看Spark UI
发现最后这个Stage切分成了40000多个task
点进去这个Stage查看task:
居然很多task都是处于空跑的状态
排查代码,一个个的debug查看各个DataFrame的partition
最终定位到其中一个DataFrame数据量很少,但是被分了200个分区,导致再和其他DataFrame做JOIN的时候会扩大成更多的分区,但很多分区内数据都是空的。
解决办法:缩小当前DataFrame的分区数
继续定位其他可优化点:
发现很多stage中只有一个分区,点进去其中一个看DAG中分区情况
发现读取jdbc数据就被分为默认1个分区
结合executors页面查看
妥妥的数据倾斜了
解决方法:修改jdbc读取ADB的分区数
val modelDataSql = "select pay_time, ... "
//根据时间分区
val partitionClause = Array(
"EXTRACT(month FROM pay_time) = 1 "
, "EXTRACT(month FROM pay_time) = 2 "
, "EXTRACT(month FROM pay_time) = 3 "
, "EXTRACT(month FROM pay_time) = 4 "
, "EXTRACT(month FROM pay_time) = 5 "
, "EXTRACT(month FROM pay_time) = 6 "
, "EXTRACT(month FROM pay_time) = 7 "
, "EXTRACT(month FROM pay_time) = 8 "
, "EXTRACT(month FROM pay_time) = 9 "
, "EXTRACT(month FROM pay_time) = 10 "
, "EXTRACT(month FROM pay_time) = 11 "
, "EXTRACT(month FROM pay_time) = 12 "
)
sparkSession.read.jdbc(adbProperties.getProperty("url"), s" ( $modelDataSql ) T", partitionClause, adbProperties)
优化后:
执行时间:10min
破案
前前后后四处定位问题,花了差不多三四天时间,终于解决了
总结:spark分区数,该大时就要大,该小时就要小