Spark之troubleshooting
1.yarn-client模式引起网卡流量激增问题?
一个Driver和Executor中的task频繁进行通信,通信消息特别多,通信的频率特别高,
运行完一个stage,接着运行下一个stage,又是频繁的通信。
解决:
yarn-cluster
yarn-client模式,通常咱们就只会使用在测试环境中,你写好了某个spark作业,打了一个jar包,
在某台测试机器上,用yarn-client模式去提交一下。因为测试的行为是偶尔为之的,
不会长时间连续提交大量的spark作业去测试。还有一点好处,yarn-client模式提交,
可以在本地机器观察到详细全面的log。通过查看log,可以去解决线上报错的故障(troubleshooting)、
对性能进行观察并进行性能调优。
实际上线了以后,
在生产环境中,都得用yarn-cluster模式,去提交你的spark作业。
yarn-cluster模式,就跟你的本地机器引起的网卡流量激增的问题,就没有关系了。也就是说,
就算有问题,也应该是yarn运维团队和基础运维团队之间的事情了。
他们去考虑Yarn集群里面每台机器是虚拟机还是物理机呢?网卡流量激增后会不会对其他东西产生影响呢?
如果网络流量激增,要不要给Yarn集群增加一些网络带宽等等这些东西。那就是他们俩个团队的事情了,
和你就没有关系了
大公司都是通过Yarn来进行调度,mapreduce on yarn、spark on yarn、甚至storm on yarn
2.yarn-cluster 会报JVM栈内存溢出问题?
问题描述一:
yarn-client PermGen 128M
yarn-cluster
PermGen
82M
有的时候,运行一些包含了spark sql的spark作业,可能会碰到yarn-client模式下,可以正常提交运行;
yarn-cluster模式下,可能是无法提交运行的,会报出JVM的PermGen(永久代)的内存溢出,OOM。
PermGen(永久代)-->JVM里面的一个区域,就是会放Class里面一些字符串常量这些东西的。
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。
问题解决:
spark-submit提交任务的脚本中,加入以下配置即可:
--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"
问题描述二:
spark sql,sql,要注意,一个问题
JVM Stack Memory Overflow,栈内存溢出
sql,有大量的or语句。比如where keywords='' or keywords='' or keywords=''
当达到or语句,有成百上千的时候,此时可能就会出现一个driver端的jvm stack overflow,
JVM栈内存溢出的问题
JVM栈内存溢出,基本上就是由于调用的方法层级过多,因为产生了大量的,非常深的,
超出了JVM栈深度限制的,递归。递归方法。我们的猜测,spark sql,有大量or语句的时候,
spark sql内部源码中,在解析sql,比如转换成语法树,或者进行执行计划的生成的时候,
对or的处理是递归。or特别多的话,就会发生大量的递归。
问题解决:
这种时候,建议不要搞那么复杂的spark sql语句。采用替代方案:将一条sql语句,
拆解成多条sql语句来执行。每条sql语句,就只有100个or子句以内;一条一条SQL语句来执行。
根据生产环境经验的测试,一条sql语句,100个or子句以内,是还可以的。通常情况下,
不会报那个栈内存溢出。
3.序列化导致的报错?
问题描述:
用client模式去提交spark作业,观察本地打印出来的log。如果出现了类似于Serializable、
Serialize等等字眼,报错的log,那么恭喜大家,就碰到了序列化问题导致的报错。
问题解决:
1、你的算子函数里面,如果使用到了外部的自定义类型的变量(
executor中使用到了外部变量),那么此时,就要求你的自定义类型,
必须是可序列化的。
2、如果要将自定义的类型,作为RDD的元素类型,那么自定义的类型也必须是可以序列化的
JavaPairRDD<Integer,Teacher> teacherRDD
JavaPairRDD<Integer,Student> studentRDD
studentRDD.join(teacherRDD)
publicclassTeacherimplementsSerializable{
}
publicclassStudentimplementsSerializable{
}
序列化:
1、executor中使用到了Driver端的变量(自定义对象)
3、RDD持久化的时候 _SER
4.解决算子函数返回NULL导致问题
问题描述:
在算子函数中,返回null,
有些算子函数里面,是需要我们有一个返回值的。但是,有时候,我们可能对某些值,
就是不想有什么返回值。
如果直接返回NULL的话,会报错的!!!
return actionRDD.mapToPair(newPairFunction<Row,String,Row>(){
privatestaticfinallong serialVersionUID =1L;
@Override
publicTuple2<String,Row> call(Row row)throwsException{
returnnewTuple2<String,Row>("-666",RowFactory.createRow("-999"));
returnnull
}
});
问题解决:
1、在返回的时候,返回一些特殊的值,不要返回null,比如“-999”
2、在通过算子获取到了一个RDD之后,可以对这个RDD执行filter操作,进行数据过滤。
filter内,可以对数据进行判定,如果是-999,那么就返回false,给过滤掉就可以了。
3、大家不要忘了,之前咱们讲过的那个算子调优里面的coalesce算子,在filter之后,
可以使用coalesce算子压缩一下RDD的partition的数量,让各个partition的数据比较紧凑一些。
也能提升一些性能。
5.YARN队列资源不足导致的Application直接失败
http://spark.apache.org/docs/1.6.2/configuration.html
问题描述:
yarn 队列?队列资源:mem core
如果说,你是基于yarn来提交spark。比如yarn-cluster或者yarn-client。
你可以指定提交到哪个yarn队列上的,每个队列都是可以有自己的资源的。
跟大家说一个生产环境中的,给spark用的yarn资源队列的情况:500G内存,200个cpu core。
比如说,某个spark application,在spark-submit里面你自己配了,executor,80个;每个executor,
4G内存;每个executor,2个cpu core。你的spark作业每次运行,大概要消耗掉320G内存,
以及160个cpu core。
乍看起来,咱们的队列资源,是足够的,500G内存,200个cpu core。
首先,第一点,你的spark作业实际运行起来以后,耗费掉的资源量,可能是比你在spark-submit里面
配置的,以及你预期的,是要大一些的。400G内存,190个cpu core。
那么这个时候,的确,咱们的队列资源还是有一些剩余的。但是问题是,如果你同时又提交了一个
spark作业上去,一模一样的。那就可能会出问题。
第二个spark作业,又要申请320G内存+160个cpu core。结果,发现队列资源不足。。。。
此时,可能会出现两种情况:(备注,具体出现哪种情况,跟你的YARN、Hadoop的版本,
你们公司的一些运维参数,以及配置、硬件、资源肯能都有关系)
1、YARN,发现资源不足时,你的spark作业,并没有等待在那里,等待资源的分配,而是直接打印一行fail的log,直接就fail掉了。
2、YARN,发现资源不足,你的spark作业,就等待在那里。一直等待之前的spark作业执行完,等待有资源分配给自己来执行。
问题解决:
1、在你的J2EE(我们这个项目里面,spark作业的运行,之前说过了,J2EE平台触发的,
执行spark-submit脚本),限制,同时只能提交一个spark作业到yarn上去执行,
确保一个spark作业的资源肯定是有的。
2、你应该采用一些简单的调度区分的方式,比如说,你有的spark作业可能是要长时间运行的,
比如运行30分钟;有的spark作业,可能是短时间运行的,可能就运行2分钟。
此时,都提交到一个队列上去,肯定不合适。很可能出现30分钟的作业卡住后面一大堆2分钟的作业。
分队列,可以申请(跟你们的YARN、Hadoop运维的同学申请)。你自己给自己搞两个调度队列。
每个队列的根据你要执行的作业的情况来设置。
在你的J2EE程序里面,要判断,如果是长时间运行的作业,
就干脆都提交到某一个固定的队列里面去;如果是短时间运行的作业,就统一提交到另外一个队列里面去。
这样,避免了长时间运行的作业,阻塞了短时间运行的作业。
3、你的队列里面,无论何时,只会有一个作业在里面运行。那么此时,
就应该用我们之前讲过的性能调优的手段,去将每个队列能承载的最大的资源,
分配给你的每一个spark作业,比如80个executor;6G的内存;3个cpu core。
尽量让你的spark作业每一次运行,都达到最满的资源使用率,最快的速度,最好的性能;并行度,
240个cpu core,720个task。
4、
在J2EE中,通过线程池的方式(一个线程池对应一个资源队列),来实现上述我们说的方案。
在J2EE平台里面,怎么控制你的资源队列同时只能跑一个作业???可以用线程池
来控制,
创建线程池容量只有1的这么一个线程池,每一次提交一个作业,就会到这个线程池里面去,
它空闲的时候就会有一个作业去跑,后面如果再有一个作业要跑的话,也扔到这个线程池里面,当然
它的容量只有1,后面的这些作业线程要去执行,要去启动spark作业的线程,它就会在那里排队,
这个
线程池自动的给你实现了这个排队机制,不同的作业要放到不同的资源队列里面去运行,
那就很简单嘛!
不同的作业放到不同的线程池!你可以搞多个线程池,每个线程池就对应着一个资源队列!
ExecutorService threadPool =Executors.newFixedThreadPool(1);
threadPool.submit(newRunnable(){
@Override
publicvoid run(){
}
});
spark如何提交到指定的资源队列中
补充:
1.yarn-client执行流程_Driver在整个Spark集群中的作用?
1,在客户端给我们启动一个Driver
2,去ResourceManager申请启动container
3,通知一个NodeManager在container里面启动ApplicationMaster
4,ApplicationMaster去找ResourceManager申请Executor
5,ResourceManager返回可以启动的NodeManager的地址
6,ApplicationMaster去找NodeManager启动Executor
7,Executor进程会反过来去向Driver注册上去
8,最后Driver接收到了Executor资源之后就可以去进行我们spark代码的执行了
9,执行到某个action就触发一个Job
10,DAGScheduler会划分JOB为一个个Stage
11,TaskScheduler会划分Stage为一个个Task
12,Task发送到Executor执行
13,Driver就来进行Task的调度,并接受Executor中task执行的结果
2.yarn-cluster执行流程
1.在客户端提交我们执行任务的命令,这时客户端发送请求到ResourceManager,请求启动ApplicationMaster
2.ResourceManager收到请求后,在某个NodeManager中分配Container,并启动ApplicationMaster(这个ApplicationMaster相当于Driver)
3.ApplicationMaster发送请求到ResourceManager,请求一批Container,用于启动Executor
4.Application得到ResourceManager的响应后,在NodeManager启动Executor,这里的NodeManager相当于Spark standalone模式下的Worker节点
5.当Executor启动之后,会反向注册到Driver(ApplicationMaster)中
6.接下来开始执行我们的代码,
执行到某个action就触发一个Job
1,spark-submit脚本提交spark application到ResourceManager
2,去ResourceManager申请启动ApplicationMaster
3,通知一个NodeManager去启动ApplicationMaster(Driver进程)
4,ApplicationMaster去找ResourceManager申请Executor
5,ResourceManager分配container,container代表你能启动的Executor占有的资源,包括内存+CPU
6,ApplicationMaster去找NodeManager在container里面申请启动Executor
7,Executor进程会反过来去向Driver注册上去
8,最后Driver接收到了Executor资源之后就可以去进行我们spark代码的执行了
9,执行到某个action就触发一个JOB
10,DAGScheduler会划分JOB为一个个Stage
11,TaskScheduler会划分Stage为一个个Task
12,Task发送到Executor执行
13,Driver就来进行Task的调度
到这里为止,ApplicationMaster(Driver),就知道自己有哪些资源可以用(executor)。
然后就会去执行job、拆分stage、提交stage的task,进行task调度,分配到各个executor上面去执行。
3.ApplicationMaster
yarn中的核心概念,任何要在yarn上启动的作业类型(mr、spark),都必须有一个。
每种计算框架(mr、spark),如果想要在yarn上执行自己的计算应用,那么就必须自己实现和
提供一个ApplicationMaster
相当于是实现了yarn提供的接口(spark自己开发的一个类)
yarn-client模式下,application的注册(executor的申请)和计算task的调度,是分离开来的。
standalone模式下,这两个操作都是Driver负责的。
ApplicationMaster(ExecutorLauncher)负责executor的申请;Driver负责job和stage的划分,
以及task的创建、分配和调度
4.Yarn集群分成两种节点:
ResourceManager 负责资源的调度
NodeManager
负责资源的分配、应用程序执行这些东西
5.Driver到底是什么?
我们写的spark程序,打成jar包,用spark-submit来提交。jar包中的一个main类,通过jvm的命令启动起来。
JVM进程,这个进程,其实就是咱们的Driver进程。Driver进程启动起来以后,执行我们自己写的main函数,从new SparkContext()。。。
6.总结一下yarn-client和yarn-cluster模式的不同之处:
yarn-client模式,driver运行在本地机器上的;
yarn-cluster模式,driver是运行在yarn集群上某个nodemanager节点上面的。
yarn-client会导致本地机器负责spark作业的调度,所以网卡流量会激增;
yarn-cluster模式就没有这个问题。
yarn-client的driver运行在本地,通常来说本地机器跟yarn集群都不会在一个机房的,性能可能不是特别好;
yarn-cluster模式下,driver是跟yarn集群运行在一个机房内,性能上来说,也会好一些。