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的元素类型,那么自定义的类型也必须是可以序列化的
  1. JavaPairRDD<Integer,Teacher> teacherRDD
  2. JavaPairRDD<Integer,Student> studentRDD
  3. studentRDD.join(teacherRDD)
  4. publicclassTeacherimplementsSerializable{
  5. }
  6. publicclassStudentimplementsSerializable{
  7. }
序列化:
1、executor中使用到了Driver端的变量(自定义对象)
2、RDD<Person> Person
3、RDD持久化的时候    _SER

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

问题描述:
    在算子函数中,返回null, 有些算子函数里面,是需要我们有一个返回值的。但是,有时候,我们可能对某些值, 就是不想有什么返回值。 如果直接返回NULL的话,会报错的!!!
  1. return actionRDD.mapToPair(newPairFunction<Row,String,Row>(){
  2. privatestaticfinallong serialVersionUID =1L;
  3. @Override
  4. publicTuple2<String,Row> call(Row row)throwsException{
  5. returnnewTuple2<String,Row>("-666",RowFactory.createRow("-999"));
  6. returnnull
  7. }
  8. });
问题解决:
   1、在返回的时候,返回一些特殊的值,不要返回null,比如“-999”
   2、在通过算子获取到了一个RDD之后,可以对这个RDD执行filter操作,进行数据过滤。
filter内,可以对数据进行判定,如果是-999,那么就返回false,给过滤掉就可以了。
   3、大家不要忘了,之前咱们讲过的那个算子调优里面的coalesce算子,在filter之后,
可以使用coalesce算子压缩一下RDD的partition的数量,让各个partition的数据比较紧凑一些。
也能提升一些性能。

5.YARN队列资源不足导致的Application直接失败

问题描述:
    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作业的线程,它就会在那里排队, 这个 线程池自动的给你实现了这个排队机制,不同的作业要放到不同的资源队列里面去运行, 那就很简单嘛! 不同的作业放到不同的线程池!你可以搞多个线程池,每个线程池就对应着一个资源队列!
  1. ExecutorService threadPool =Executors.newFixedThreadPool(1);
  2. threadPool.submit(newRunnable(){
  3. @Override
  4. publicvoid run(){
  5. }
  6. });
 
   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
返回已经启
   
   
   
 
container的NodeManager的地址
   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自己开发的一个类)
   
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集群运行在一个机房内,性能上来说,也会好一些。
 

猜你喜欢

转载自blog.csdn.net/qq_30062385/article/details/79703705