spark之OOM常见问题梳理(一)

我想变成那陪着疲倦的你一直看海的小小的白色的椅子。
—王小波
spark之OOM常见问题梳理(一)_第1张图片

一、Spark报错信息

问题一

1.描述 org.apache.spark.shuffle.FetchFailedException
这种问题一般发生在有大量shuffle操作的时候,task不断的failed,然后又重执行,一直循环下去,非常的耗时。
图片
2.报错提示
(1) missing output location
org.apache.spark.shuffle.MetadataFetchFailedException: Missing an output location for shuffle 0
(2) shuffle fetch faild
org.apache.spark.shuffle.FetchFailedException: Failed to connect to spark047215/192.168.47.215:50268
当前的配置为每个executor使用1cpu,5G内存,启动了20个executor
3.解决方案
一般遇到这种问题提高executor内存即可,同时增加每个executor的cpu,这样不会减少task并行度。
spark.executor.memory 15G
spark.executor.cores 3
spark.cores.max 21
启动的execuote数量为:7个
execuoteNum = spark.cores.max/spark.executor.cores
每个executor的配置:
3core,15G RAM
消耗的内存资源为:105G RAM
15G*7=105G
可以发现使用的资源并没有提升,但是同样的任务原来的配置跑几个小时还在卡着,改了配置后几分钟就结束了。

问题二

1.描述 Executor&Task Lost
因为网络或者gc的原因,worker或executor没有接收到executor或task的心跳反馈
2.报错提示
(1) executor lost
WARN TaskSetManager: Lost task 1.0 in stage 0.0 (TID 1, aa.local): ExecutorLostFailure (executor lost)
(2) task lost
WARN TaskSetManager: Lost task 69.2 in stage 7.0 (TID 1145, 192.168.47.217): java.io.IOException:
Connection from /192.168.47.217:55483 closed
(3) 各种timeout
ERROR TransportChannelHandler: Connection to /192.168.47.212:35409 has been quiet for 120000 ms while there are outstanding requests.
Assuming connection is dead; please adjust spark.network.timeout if this is wrong
3.解决方案
提高 spark.network.timeout 的值,根据情况改成300(5min)或更高。
默认为 120(120s),配置所有网络传输的延时,如果没有主动设置以下参数,默认覆盖其属性

spark.core.connection.ack.wait.timeout
spark.akka.timeout
spark.storage.blockManagerSlaveTimeoutMs
spark.shuffle.io.connectionTimeout
spark.rpc.askTimeout or spark.rpc.lookupTimeout

问题三

1.倾斜 问题描述
大多数任务都完成了,还有那么一两个任务怎么都跑不完或者跑的很慢。
分为数据倾斜和task倾斜两种。
2.错误提示
(1) 数据倾斜
图片
(2) 任务倾斜
差距不大的几个task,有的运行速度特别慢。
3.解决方案
(1) 数据倾斜
数据倾斜大多数情况是由于大量null值或者"“引起,在计算前过滤掉这些数据既可。
例如:
sqlContext.sql(”…where col is not null and col != ‘’")
(2) 任务倾斜
task倾斜原因比较多,网络io,cpu,mem都有可能造成这个节点上的任务执行缓慢,可以去看该节点的性能监控来分析原因。以前遇到过同事在spark的一台worker上跑R的任务导致该节点spark task运行缓慢。
或者可以开启spark的推测机制,开启推测机制后如果某一台机器的几个task特别慢,推测机制会将任务分配到其他机器执行,最后Spark会选取最快的作为最终结果。

spark.speculation true
spark.speculation.interval 100 - 检测周期,单位毫秒;
spark.speculation.quantile 0.75 - 完成task的百分比时启动推测
spark.speculation.multiplier 1.5 - 比其他的慢多少倍时启动推测。

问题四

1.问题描述
内存不够,数据太多就会抛出OOM的Exeception
2.解决方案
主要有driver OOM和executor OOM两种
(1) driver OOM
一般是使用了collect操作将所有executor的数据聚合到driver导致。尽量不要使用collect操作即可。
(2) executor OOM
可以按下面的内存优化的方法增加code使用内存空间
增加executor内存总量,也就是说增加spark.executor.memory的值
增加任务并行度(大任务就被分成小任务了),参考下面优化并行度的方法
优化
1.内存
当然如果你的任务shuffle量特别大,同时rdd缓存比较少可以更改下面的参数进一步提高任务运行速度。
spark.storage.memoryFraction - 分配给rdd缓存的比例,默认为0.6(60%),如果缓存的数据较少可以降低该值。
spark.shuffle.memoryFraction - 分配给shuffle数据的内存比例,默认为0.2(20%)
剩下的20%内存空间则是分配给代码生成对象等。
如果任务运行缓慢,jvm进行频繁gc或者内存空间不足,或者可以降低上述的两个值。
“spark.rdd.compress”,“true” - 默认为false,压缩序列化的RDD分区,消耗一些cpu减少空间的使用
如果数据只使用一次,不要采用cache操作,因为并不会提高运行速度,还会造成内存浪费。
2.并行度
spark.default.parallelism
发生shuffle时的并行度,在standalone模式下的数量默认为core的个数,也可手动调整,数量设置太大会造成很多小任务,增加启动任务的开销,太小,运行大数据量的任务时速度缓慢。
spark.sql.shuffle.partitions
sql聚合操作(发生shuffle)时的并行度,默认为200,如果任务运行缓慢增加这个值。
相同的两个任务:
spark.sql.shuffle.partitions=300:
spark.sql.shuffle.partitions=500:
速度变快主要是大量的减少了gc的时间。
修改map阶段并行度主要是在代码中使用rdd.repartition(partitionNum)来操作。

二、Spark之OOM常见原因分类

OutOfMemoryError:GC overhead limit exceeded,Java heap space的解决方案
因为之前spark程序运算量不是特别大,关于提交时申请的集群资源就一直没有变动,后来数据不断增大,导致程序出现以下异常:

java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError:GC overhead limit exceeded

spark属性方面调整:

一般这两个异常是由于executor或者driver内存设置的不够导致的,driver设置过小的情况不过相对较小,一般是由于executoer内存不足导致的。不过不论是哪种情况,我们都可以通过提交命令或者是spark的配置文件指定driver-memory和executor-memory的内存大小来解决问题。

spark-submit --master yarn-cluster --class MAIN_CLASS \
--executor-memory 10G \
--executor-cores 10 --driver-memory 2g --name APP_NAME

代码方面调整建议:

其实当数据量越大时,越能体现出代码质量的重要性,所以出现oom的问题也应该从代码方向看一下是否还有调整优化的空间,特别是针对RDD操作的代码。比如,RDD是否还需要重用进行多次操作,如果是我们就可以使用cache()和persist()选择不同的缓存策略,不但提高下次操作时的执行效率,并且还能节省创建RDD占用的内存。
另外Transformation 操作是延迟计算的,也就是说从一个RDD 转换生成另一个 RDD 的转换操作不是马上执行,需要等到有 Action 操作的时候才会真正触发运算。

算子的选择:

例如:mapPartitionsToPair虽然能提高spark的执行效率,但如果数据量过大内存不足在进行算子操作时,也会有可能跑出java heap space异常
另外还有算子内操作尽量能用基本数据类型就不用引用类型,能用数组就不用集合,另外还比如字符串拼接,用StringBuffer代替+连接等等。这些方式不但可以节省空间还能增加算子的执行效率。

三、Spark常见问题处理

1.shuffle reduce端缓冲大小以避免OOM

map端的task是不断的输出数据的,数据量可能是很大的。但是,其实reduce端的task,并不是等到map端task将属于自己的那份数据全部写入磁盘文件之后,再去拉取的。map端写一点数据,reduce端task就会拉取一小部分数据,立即进行后面的聚合、算子函数的应用。每次reduece能够拉取多少数据,就由buffer来决定。因为拉取过来的数据,都是先放在buffer中的。然后才用后面的executor分配的堆内存占比(0.2),hashmap,去进行后续的聚合、函数的执行。
1.1.reduce端缓冲(buffer),可能会出什么问题?
可能是会出现,默认是48MB,也许大多数时候,reduce端task一边拉取一边计算,不一定一直都会拉满48M的数据。可能大多数时候,拉取个10M数据,就计算掉了。
大多数时候,也许不会出现什么问题。但是有的时候,map端的数据量特别大,然后写出的速度特别快。reduce端所有task,拉取的时候,全部达到自己的缓冲的最大极限值,缓冲,48M,全部填满。
这个时候,再加上你的reduce端执行的聚合函数的代码,可能会创建大量的对象。也许,一下子,内存就撑不住了,就会OOM。reduce端的内存中,就会发生内存溢出的问题。
1.2.问题,我们该怎么来解决呢?
这个时候,就应该减少reduce端task缓冲的大小。我宁愿多拉取几次,但是每次同时能够拉取到reduce端每个task的数量,比较少,就不容易发生OOM内存溢出的问题。(比如,可以调节成12M)
在实际生产环境中,我们都是碰到过这种问题的。这是典型的以性能换执行的原理。reduce端缓冲小了,不容易OOM了,但是,性能一定是有所下降的,你要拉取的次数就多了。就走更多的网络传输开销。
这种时候,只能采取牺牲性能的方式了,spark作业,首先,第一要义,就是一定要让它可以跑起来。分享一个经验,曾经写过一个特别复杂的spark作业,写完代码以后,半个月之内,就是跑不起来,里面各种各样的问题,需要进行troubleshooting。调节了十几个参数,其中就包括这个reduce端缓冲的大小。总算作业可以跑起来了。
spark.reducer.maxSizeInFlight=48 改为spark.reducer.maxSizeInFlight=24 减少reduce端task缓冲的大小。我宁愿多拉取几次

2.JVM GC导致的shuffle文件拉取失败

2.1.问题描述
有时会出现的一种情况,非常普遍,在spark的作业中;shuffle file not found。(spark作业中,非常非常常见的)而且,有的时候,它是偶尔才会出现的一种情况。有的时候,出现这种情况以后,会重新去提交stage、task。重新执行一遍,发现就好了。没有这种错误了。log怎么看?用client模式去提交你的spark作业。比如standalone client;yarn client。一提交作业,直接可以在本地看到刷刷刷更新的log。
比如,executor的JVM进程,可能内存不是很够用了。那么此时可能就会执行GC。minor GC or full GC。总之一旦发生了JVM之后,就会导致executor内,所有的工作线程全部停止,比如BlockManager,基于netty的网络通信。
下一个stage的executor,可能是还没有停止掉的,task想要去上一个stage的task所在的exeuctor,去拉取属于自己的数据,结果由于对方正在gc,就导致拉取了半天没有拉取到。就很可能会报出,shuffle file not found。但是,可能下一个stage又重新提交了stage或task以后,再执行就没有问题了,因为可能第二次就没有碰到JVM在gc了。
2.2. 问题修改
spark.shuffle.io.maxRetries=3
第一个参数,意思就是说,shuffle文件拉取的时候,如果没有拉取到(拉取失败),最多或重试几次(会重新拉取几次文件),默认是3次。
spark.shuffle.io.retryWait=5s
第二个参数,意思就是说,每一次重试拉取文件的时间间隔,默认是5s钟。
默认情况下,假如说第一个stage的executor正在进行漫长的full gc。第二个stage的executor尝试去拉取文件,结果没有拉取到,默认情况下,会反复重试拉取3次,每次间隔是五秒钟。最多只会等待3 * 5s = 15s。如果15s内,没有拉取到shuffle file。就会报出shuffle file not found。
针对这种情况,我们完全可以进行预备性的参数调节。增大上述两个参数的值,达到比较大的一个值,尽量保证第二个stage的task,一定能够拉取到上一个stage的输出文件。避免报shuffle file not found。然后可能会重新提交stage和task去执行。那样反而对性能也不好。

3.YARN队列资源不足导致的application直接失败

3.1.现象
如果说,你是基于yarn来提交spark。比如yarn-cluster或者yarn-client。你可以指定提交到某个hadoop队列上的。每个队列都是可以有自己的资源的。
假如我们的环境给spark用的yarn资源队列的情况:500G内存,200个cpu core。
比如说,某个spark application,在spark-submit里面你自己配了,executor,80个;每个executor,4G内存;每个executor,2个cpu core。你的spark作业每次运行,大概要消耗掉320G内存,以及160个cpu core。
乍看起来,咱们的队列资源,是足够的,500G内存,280个cpu core。
首先,第一点,你的spark作业实际运行起来以后,耗费掉的资源量,可能是比你在spark-submit里面配置的,以及你预期的,是要大一些的。400G内存,190个cpu core。
那么这个时候,的确,咱们的队列资源还是有一些剩余的。但是问题是,如果你同时又提交了一个spark作业上去,一模一样的。那就可能会出问题。
第二个spark作业,又要申请320G内存+160个cpu core。结果,发现队列资源不足。。。。
此时,可能会出现两种情况:(备注,具体出现哪种情况,跟你的YARN、Hadoop的版本,你们公司的一些运维参数,以及配置、硬件、资源肯能都有关系)
YARN,发现资源不足时,你的spark作业,并没有hang在那里,等待资源的分配,而是直接打印一行fail的log,直接就fail掉了。
YARN,发现资源不足,你的spark作业,就hang在那里。一直等待之前的spark作业执行完,等待有资源分配给自己来执行。
此时,可能会出现两种情况:(备注,具体出现哪种情况,跟你的YARN、Hadoop的版本,你们公司的一些运维参数,以及配置、硬件、资源肯能都有关系)
3.2.应对方案
在你的J2EE(我们这个项目里面,spark作业的运行,之前说过了,J2EE平台触发的,执行spark-submit脚本),限制,同时只能提交一个spark作业到yarn上去执行,确保一个spark作业的资源肯定是有的。
你应该采用一些简单的调度区分的方式,比如说,你有的spark作业可能是要长时间运行的,比如运行30分钟;有的spark作业,可能是短时间运行的,可能就运行2分钟。此时,都提交到一个队列上去,肯定不合适。很可能出现30分钟的作业卡住后面一大堆2分钟的作业。分队列,可以申请(跟你们的YARN、Hadoop运维的同学申请)。你自己给自己搞两个调度队列。每个队列的根据你要执行的作业的情况来设置。在你的J2EE程序里面,要判断,如果是长时间运行的作业,就干脆都提交到某一个固定的队列里面去把;如果是短时间运行的作业,就统一提交到另外一个队列里面去。这样,避免了长时间运行的作业,阻塞了短时间运行的作业。
你的队列里面,无论何时,只会有一个作业在里面运行。那么此时,就应该用我们之前讲过的性能调优的手段,去将每个队列能承载的最大的资源,分配给你的每一个spark作业,比如80个executor;6G的内存;3个cpu core。尽量让你的spark作业每一次运行,都达到最满的资源使用率,最快的速度,最好的性能;并行度,240个cpu core,720个task。
在J2EE中,通过线程池的方式(一个线程池对应一个资源队列),来实现上述我们说的方案。

4.解决各种序列化导致的报错

4.1.报错问题
用client模式去提交spark作业,观察本地打印出来的log。如果出现了类似于Serializable、Serialize等等字眼,报错的log,那么恭喜大家,就碰到了序列化问题导致的报错。
虽然是报错,但是序列化报错,应该是属于比较简单的了,很好处理。
序列化报错要注意的三个点:
你的算子函数里面,如果使用到了外部的自定义类型的变量,那么此时,就要求你的自定义类型,必须是可序列化的。
如果要将自定义的类型,作为RDD的元素类型,那么自定义的类型也必须是可以序列化的
不能在上述两种情况下,去使用一些第三方的,不支持序列化的类型

5.解决算子函数返回NULL导致的问题

大家可以看到,在有些算子函数里面,是需要我们有一个返回值的。但是,有时候,我们可能对某些值,就是不想有什么返回值。我们如果直接返回NULL的话,那么可以不幸的告诉大家,是不行的,会报错的。
Scala.Math(NULL),异常
如果碰到你的确是对于某些值,不想要有返回值的话,有一个解决的办法:
在返回的时候,返回一些特殊的值,不要返回null,比如“-999”
在通过算子获取到了一个RDD之后,可以对这个RDD执行filter操作,进行数据过滤。filter内,可以对数据进行判定,如果是-999,那么就返回false,给过滤掉就可以了。
大家不要忘了,之前咱们讲过的那个算子调优里面的coalesce算子,在filter之后,可以使用coalesce算子压缩一下RDD的partition的数量,让各个partition的数据比较紧凑一些。也能提升一些性能。

6.解决yarn-client模式导致的网卡流量激增问题

6.1.yarn-client模式下,会产生什么样的问题呢?
由于咱们的driver是启动在本地机器的,而且driver是全权负责所有的任务的调度的,也就是说要跟yarn集群上运行的多个executor进行频繁的通信(中间有task的启动消息、task的执行统计消息、task的运行状态、shuffle的输出结果)。
咱们来想象一下。比如你的executor有100个,stage有10个,task有1000个。每个stage运行的时候,都有1000个task提交到executor上面去运行,平均每个executor有10个task。接下来问题来了,driver要频繁地跟executor上运行的1000个task进行通信。通信消息特别多,通信的频率特别高。运行完一个stage,接着运行下一个stage,又是频繁的通信。
在整个spark运行的生命周期内,都会频繁的去进行通信和调度。所有这一切通信和调度都是从你的本地机器上发出去的,和接收到的。这是最要人命的地方。你的本地机器,很可能在30分钟内(spark作业运行的周期内),进行频繁大量的网络通信。那么此时,你的本地机器的网络通信负载是非常非常高的。会导致你的本地机器的网卡流量会激增!!!
你的本地机器的网卡流量激增,当然不是一件好事了。因为在一些大的公司里面,对每台机器的使用情况,都是有监控的。不会允许单个机器出现耗费大量网络带宽等等这种资源的情况。运维人员。可能对公司的网络,或者其他(你的机器还是一台虚拟机),对其他机器,都会有负面和恶劣的影响。
6.2.解决方法
实际上解决的方法很简单,就是心里要清楚,yarn-client模式是什么情况下,可以使用的?yarn-client模式,通常咱们就只会使用在测试环境中,你写好了某个spark作业,打了一个jar包,在某台测试机器上,用yarn-client模式去提交一下。因为测试的行为是偶尔为之的,不会长时间连续提交大量的spark作业去测试。还有一点好处,yarn-client模式提交,可以在本地机器观察到详细全面的log。通过查看log,可以去解决线上报错的故障(troubleshooting)、对性能进行观察并进行性能调优。
实际上线了以后,在生产环境中,都得用yarn-cluster模式,去提交你的spark作业。
yarn-cluster模式,就跟你的本地机器引起的网卡流量激增的问题,就没有关系了。也就是说,就算有问题,也应该是yarn运维团队和基础运维团队之间的事情了。使用了yarn-cluster模式以后,就不是你的本地机器运行Driver,进行task调度了。是yarn集群中,某个节点会运行driver进程,负责task调度。

7.yarn-cluster模式的JVM内存溢出无法执行问题

yarn-client模式,driver运行在本地机器上的;yarn-cluster模式,driver是运行在yarn集群上某个nodemanager节点上面的。
yarn-client会导致本地机器负责spark作业的调度,所以网卡流量会激增;yarn-cluster模式就没有这个问题。
yarn-client的driver运行在本地,通常来说本地机器跟yarn集群都不会在一个机房的,所以说性能可能不是特别好;yarn-cluster模式下,driver是跟yarn集群运行在一个机房内,性能上来说,也会好一些。
7.1.碰到的yarn-cluster的问题:
有的时候,运行一些包含了spark sql的spark作业,可能会碰到yarn-client模式下,可以正常提交运行;yarn-cluster模式下,可能是无法提交运行的,会报出JVM的PermGen(永久代)的内存溢出,OOM。
yarn-client模式下,driver是运行在本地机器上的,spark使用的JVM的PermGen的配置,是本地的spark-class文件(spark客户端是默认有配置的),JVM的永久代的大小是128M,这个是没有问题的;但是呢,在yarn-cluster模式下,driver是运行在yarn集群的某个节点上的,使用的是没有经过配置的默认设置(PermGen永久代大小),82M。
spark-sql,它的内部是要进行很复杂的SQL的语义解析、语法树的转换等等,特别复杂,在这种复杂的情况下,如果说你的sql本身特别复杂的话,很可能会比较导致性能的消耗,内存的消耗。可能对PermGen永久代的占用会比较大。
所以,此时,如果对永久代的占用需求,超过了82M的话,但是呢又在128M以内;就会出现如上所述的问题,yarn-client模式下,默认是128M,这个还能运行;如果在yarn-cluster模式下,默认是82M,就有问题了。会报出PermGen Out of Memory error log。
7.2.解决方案
既然是JVM的PermGen永久代内存溢出,那么就是内存不够用。咱们呢,就给yarn-cluster模式下的,driver的PermGen多设置一些。
spark-submit脚本中,加入以下配置即可:
–conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"
这个就设置了driver永久代的大小,默认是128M,最大是256M。那么,这样的话,就可以基本保证你的spark作业不会出现上述的yarn-cluster模式导致的永久代内存溢出的问题。
7.3. spark sql
要注意,一个问题sql,有大量的or语句。比如where keywords=’’ or keywords=’’ or keywords=’’
当达到or语句,有成百上千的时候,此时可能就会出现一个driver端的jvm stack overflow,JVM栈内存溢出的问题
JVM栈内存溢出,基本上就是由于调用的方法层级过多,因为产生了大量的,非常深的,超出了JVM栈深度限制的,递归。递归方法。我们的猜测,spark sql,有大量or语句的时候,spark sql内部源码中,在解析sql,比如转换成语法树,或者进行执行计划的生成的时候,对or的处理是递归。or特别多的话,就会发生大量的递归。
JVM Stack Memory Overflow,栈内存溢出。
这种时候,建议不要搞那么复杂的spark sql语句。采用替代方案:将一条sql语句,拆解成多条sql语句来执行。每条sql语句,就只有100个or子句以内;一条一条SQL语句来执行。根据生产环境经验的测试,一条sql语句,100个or子句以内,是还可以的。通常情况下,不会报那个栈内存溢出。

8.错误的持久化方式以及checkpoint的使用

8.1.错误的持久化使用方式:
如现在有一个usersRDD,想要对这个RDD做一个cache,希望能够在后面多次使用这个RDD的时候,不用反复重新计算RDD;可以直接使用通过各个节点上的executor的BlockManager管理的内存 / 磁盘上的数据,避免重新反复计算RDD。

usersRDD.cache()  
usersRDD.count()  
usersRDD.take()

上面这种方式,不要说会不会生效了,实际上是会报错的。会报什么错误呢?会报一大堆file not found的错误。
正确的持久化使用方式:

usersRDD
usersRDD = usersRDD.cache()
val cachedUsersRDD = usersRDD.cache()

之后再去使用usersRDD,或者cachedUsersRDD,就可以了。就不会报错了。所以说,这个是咱们的持久化的正确的使用方式。
8.2.持久化遇到的问题:
持久化,大多数时候,都是会正常工作的。但是就怕,有些时候,会出现意外。
比如说,缓存在内存中的数据,可能莫名其妙就丢失掉了。或者说,存储在磁盘文件中的数据,莫名其妙就没了,文件被误删了。
出现上述情况的时候,接下来,如果要对这个RDD执行某些操作,可能会发现RDD的某个partition找不到了。对消失的partition重新计算,计算完以后再缓存和使用。
有些时候,计算某个RDD,可能是极其耗时的。可能RDD之前有大量的父RDD。那么如果你要重新计算一个partition,可能要重新计算之前所有的父RDD对应的partition。这种情况下,就可以选择对这个RDD进行checkpoint,以防万一。进行checkpoint,就是说,会将RDD的数据,持久化一份到容错的文件系统上(比如hdfs)。在对这个RDD进行计算的时候,如果发现它的缓存数据不见了。优先就是先找一下有没有checkpoint数据(到hdfs上面去找)。如果有的话,就使用checkpoint数据了。不至于说是去重新计算。
checkpoint,其实就是可以作为是cache的一个备胎。如果cache失效了,checkpoint就可以上来使用了。checkpoint有利有弊,利在于,提高了spark作业的可靠性,一旦发生问题,还是很可靠的,不用重新计算大量的rdd;但是弊在于,进行checkpoint操作的时候,也就是将rdd数据写入hdfs中的时候,还是会消耗性能的。checkpoint,用性能换可靠性。
8.3.checkpoint原理:
在代码中,用SparkContext,设置一个checkpoint目录,可以是一个容错文件系统的目录,比如hdfs;
在代码中,对需要进行checkpoint的rdd,执行RDD.checkpoint();
RDDCheckpointData(spark内部的API),接管你的RDD,会标记为marked for checkpoint,准备进行checkpoint
你的job运行完之后,会调用一个finalRDD.doCheckpoint()方法,会顺着rdd lineage,回溯扫描,发现有标记为待checkpoint的rdd,就会进行二次标记,inProgressCheckpoint,正在接受checkpoint操作
job执行完之后,就会启动一个内部的新job,去将标记为inProgressCheckpoint的rdd的数据,都写入hdfs文件中。(备注,如果rdd之前cache过,会直接从缓存中获取数据,写入hdfs中;如果没有cache过,那么就会重新计算一遍这个rdd,再checkpoint)
将checkpoint过的rdd之前的依赖rdd,改成一个CheckpointRDD*,强制改变你的rdd的lineage。后面如果rdd的cache数据获取失败,直接会通过它的上游CheckpointRDD,去容错的文件系统,比如hdfs中,获取checkpoint的数据。
8.4 cache 与 checkpoint 的区别?
rdd.persist(StorageLevel.DISK_ONLY) 与 checkpoint 也有区别。
前者虽然可以将 RDD 的 partition 持久化到磁盘,但该 partition 由 blockManager 管理。一旦 driver program 执行结束,也就是 executor 所在进程 CoarseGrainedExecutorBackend stop,blockManager 也会 stop,被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除);
而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉(话说怎么 remove checkpoint 过的 RDD?),是一直存在的,也就是说可以被下一个 driver program 使用,而 cached RDD 不能被其他 dirver program 使用。
8.5 for example:

package internals

import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
object groupByKeyTest {
   def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("GroupByKey").setMaster("local")
    val sc = new SparkContext(conf) 
    sc.setCheckpointDir("/Users/xulijie/Documents/data/checkpoint")

    val data = Array[(Int, Char)]((1, 'a'), (2, 'b'),
                                     (3, 'c'), (4, 'd'),
                                     (5, 'e'), (3, 'f'),
                                     (2, 'g'), (1, 'h')
                                    )                               
    val pairs = sc.parallelize(data, 3)  
    pairs.checkpoint
    pairs.count

    val result = pairs.groupByKey(2)
    result.foreachWith(i => i)((x, i) => println("[PartitionIndex " + i + "] " + x))
    println(result.toDebugString)
   }
}

四、常见内存报错

1)空指针异常

报错:
java.lang.NullPointerException at com.immomo.recommend.recommend_molive anonfun 1.apply(recommend_molive.scala:83)
处理:该问题一般是代码中的,检查数组,对象内容是否可能为空;尤其是表数据,能有字段的值为null,但没有处理null,出现这个错误。

2)kyro 缓存溢出

报错:
java.lang.OutOfMemoryError: Java heap space at com.esotericsoftware.kryo.io.Output.require(Output.java:168)
处理:该报错堆栈可以看到是kyro请求空间,结果不够出现溢出,因为kyro序列化器能序列化的单个对象最大限制为spark.kryoserializer.buffer.max定义,这个值最大为2g。所以建议优先检查代码中的大对象,想办法裁剪对象大小,如果不行再考虑增大spark.kryoserializer.buffer.max数值。

3)container内存不足被kill

报错:
Job aborted due to stage failure ExecutorLostFailure (executor 2101 exited caused by one of the running tasks) Reason: Container marked as failed: container_1491814332016_46280_01_009179 on host
处理:
增大分区数,使用 set spark.sql.shuffle.partitions=1000(或更大)
调整代码,减少数据读取量

4)单个分区数据空间超过2G

报错:
java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:828) at org.apache.spark.storage.DiskStoreKaTeX parse error: Can't use function '$' in math mode at position 8: anonfun$̲getBytes$2.appl…anonfun$getBytes 2. a p p l y ( D i s k S t o r e . s c a l a : 91 ) a t o r g . a p a c h e . s p a r k . u t i l . U t i l s 2.apply(DiskStore.scala:91) at org.apache.spark.util.Utils 2.apply(DiskStore.scala:91)atorg.apache.spark.util.Utils.tryWithSafeFinally(Utils.scala:1307) at org.apache.spark.storage.DiskStore.getBytes(DiskStore.scala:105) at org.apache.spark.storage.BlockManager.org a p a c h e apache apachespark s t o r a g e storage storageBlockManager d o G e t L o c a l B y t e s ( B l o c k M a n a g e r . s c a l a : 496 ) a t o r g . a p a c h e . s p a r k . s t o r a g e . B l o c k M a n a g e r doGetLocalBytes(BlockManager.scala:496) at org.apache.spark.storage.BlockManager doGetLocalBytes(BlockManager.scala:496)atorg.apache.spark.storage.BlockManageranonfun$getLocalBytes 2. a p p l y ( B l o c k M a n a g e r . s c a l a : 474 ) a t o r g . a p a c h e . s p a r k . s t o r a g e . B l o c k M a n a g e r 2.apply(BlockManager.scala:474) at org.apache.spark.storage.BlockManager 2.apply(BlockManager.scala:474)atorg.apache.spark.storage.BlockManager a n o n f u n anonfun anonfungetLocalBytes$2.apply(BlockManager.scala:474) at scala.Option.map(Option.scala:146)
处理:该问题是分区数据存储的时候出现报错,因为单个分区上限是2G,超过该限制则报错,解决方法是调大分区,使用repartition或对含有shuffle算子指定一个大分区即可。
不仅如此,shuffle 过程fetch block的最大size也是2G。此外还有多项与2G有关的limitation需要注意

五、其他问题

1)代码不规范

报错:
org.apache.spark.SparkException: This RDD lacks a SparkContext. It could happen in the following cases: (1) RDD transformations and actions are NOT invoked by the driver, but inside of other transformations; for example, rdd1.map(x => rdd2.values.count() * x) is invalid because the values transformation and count action cannot be performed inside of the rdd1.map transformation. For more information, see SPARK-5063.
处理:这个报错是因为RDD的transformation中嵌套transformation或action,导致计算失败,可以先从报错那一行找到嵌套的trans或action操作,把这个操作拿出来运算。

2)磁盘临时文件空间不足

报错:
java.io.IOException: No space left on device
处理:在shuffle过程中,中间文件都放在/tmp目录,当shuffle文件达到磁盘空间上限,就报错。解决方法可以增大executor个数,分担压力,如果仍不可以的话就联系平台同学配置spark-default.conf中设置spark.local.dir(默认是/tmp)为磁盘空间足够的目录即可解决。在yarn模式则配置LOCAL_DIRS。

3)文件没有访问权限

报错:
Caused by: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.AccessControlException): Permission denied: user=dm, access=EXECUTE, inode="/user/hadoop/.sparkStaging/application_1480755301936_1884":hadoop:supergroup:drwx------
处理:查看这个job是什么用户执行,要确定任务执行的权限,一般是使用其他组件调用,导致执行用户变化,导致没有文件权限。

4)yarn cluster模式使用SQL找不到表

报错:
org.apache.spark.sql.AnalysisException: Table or view not found: at org.apache.spark.sql.catalyst.analysis.package A n a l y s i s E r r o r A t . f a i l A n a l y s i s ( p a c k a g e . s c a l a : 42 ) a t o r g . a p a c h e . s p a r k . s q l . c a t a l y s t . a n a l y s i s . A n a l y z e r AnalysisErrorAt.failAnalysis(package.scala:42) at org.apache.spark.sql.catalyst.analysis.Analyzer AnalysisErrorAt.failAnalysis(package.scala:42)atorg.apache.spark.sql.catalyst.analysis.AnalyzerResolveRelations$.getTable(Analyzer.scala:306)
处理:在提交代码参数中增加 --files ***/hive-site.xml,参数,表示提交代码时提交hive相关配置信息。

5) 参数提交顺序不当,导致job不能提交到yarn

有同学submit任务参数顺序不当,导致参数没有传递成功,一般–class参数放后面,–master --conf之类参数在前面

6) 系统存在多版本python情况下执行bin/pyspark报错

报错:
`pyenv: python2.7: command not found
The python2.7’ command exists in these Python versions: 2.7.7 2.7.8
处理:手动指定使用的python版本,如执行: pyenv shell 2.7.8

7) jdbc连接hiveserver2出错

报错:
ExecuteStatement failed: out of sequence response 或者Read a negative frame size (-2147418110)!
处理方法:参考HIVE-10410的patch

8) 使用spark sql查询报错文件找不到

报错:
java.io.IOException not a file: hdfs:// **** java.sql.SQLException
处理:设置参数即可,SET mapred.input.dir.recursive=true; SET hive.mapred.supports.subdirectories=true;

9) 账户拆分,导致执行spark sql没权限

报错:
py4j.protocol.Py4JJavaError: An error occurred while calling o205.sql. : java.lang.RuntimeException: java.lang.RuntimeException: java.io.IOException: Permission denied … Caused by: java.io.IOException: Permission denied at java.io.UnixFileSystem.createFileExclusively(Native Method) at java.io.File.createNewFile(File.java:1006) at java.io.File.createTempFile(File.java:1989) at org.apache.hadoop.hive.ql.session.SessionState.createTempFile(SessionState.java:818) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:513)
解决方法:查询得知是参数hive.exec.local.scratchdir对应的路径没有权限,刷权限后解决。

11) Container marked as failed

问题:
scheduler.TaskSetManager: Lost task 53.0 in stage 2.2 (TID 440, bigdata38.webmedia.int): ExecutorLostFailure (executor 9 exited caused by one of the running tasks) Reason: Container marked as failed: container_e50_1490337980512_0006_01_000010 on host: bigdata38.webmedia.int. Exit status: 143. Diagnostics: Container killed on request. Exit code is 143
ERROR CoarseGrainedExecutorBackend: RECEIVED SIGNAL TERM
org.apache.spark.rpc.RpcTimeoutException: Futures timed out after [20 seconds]. This timeout is controlled by spark.executor.heartbeatInterval

(以上是分析不同进程日志汇总得到)
问题原因:虽然以上报错不能直接看出原因,但大概率与内存相关,因为作业内存不足,导致GC,GC可能导致executor与AM通信超时,故AM认为executor挂了,会发停止的signal。
解决:
增加硬件资源
增大作业并发度,加大executor通信超时时间spark.executor.heartbeatInterval

12)Spark 读取Hbase 映射到Hive中的外部表报

报错:
java.lang.NoSuchMethodError: org.apache.hadoop.hive.serde2.lazy.LazySim
java.lang.NoSuchMethodError: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe.initSerdeParams(Lorg/apache/hadoop/conf/Configuration;Ljava/util/Properties;Ljava/lang/String;)Lorg/apache/hadoop/hive/serde2/lazy/LazySimpleSerDe$SerDeParameters;

spark执行hive引入的hbase外部表,需要在spark的jars中加入hbase相关的包,除了包含hbase的包,还需要htrace-core-2.04.jar、hive-serde-**.jar
加入进入后还要引入hbase-site.xml到$SPARK_HOME/conf中

未完待续…

获取更多资料详情关注公众号:
spark之OOM常见问题梳理(一)_第2张图片

你可能感兴趣的:(spark,spark,大数据)