前言
Spark是专为大规模数据处理而设计的快速通用的计算引擎,具有速度快、支持多语言、移植性高的特点。而移植性高的体现就在于Spark的部署方式有多种模式,如:本地local、Standalone、Apache Mesos、Hadoop YARN、EC2、Mesos、K8S等等。
背景
一般公司的大数据项目基础设施都是建立在hdfs之上,所以在大部分的公司里,Spark都是跑在Yarn之上,yarn作为一个资源调度器并不能感知Spark作业具体需要分配多少资源,那就需要程序员在提交Spark作业的时候,设置作业所需要的资源向Yarn申请。 资源参数设置的不合理,可能会导致没有充分利用集群资源,作业运行会极其缓慢;或者设置的资源过大,队列没有足够的资源来提供,进而导致各种异常。总之,无论是哪种情况,都会导致Spark作业的运行效率低下,甚至根本无法运行。因此我们必须对Spark作业的资源使用原理有一个清晰的认识,并知道在Spark作业运行过程中,有哪些资源参数是可以设置的,以及如何设置合适的参数值。
spark的yarn-client提交流程
- 在client端启动Driver进程,初始化作业,解析程序,初始化两个DAGScheduler,TaskScheduler. – 初始化作业: 判断路径是否存在,权限校验等 – DAGScheduler将程序的执行流程解析成DAG图,并划分阶段,根据阶段内的分区初始化Task – TaskScheduler接收Task,等待分配Task给executor
- Driver会向ResourceManager,申请资源,想要启动该应用程序的AppMaster
- ResourceManager会分配一个节点,来运行AppMaster,由NodeManager负责真正分配资源运行AppMaster
- AppMaster会向ResourceManager申请整个程序所需要的其他资源,准备运行executor进程
- 在各个节点上运行的executor会向Driver进行反向注册,要求分配任务
- TaskScheduler将Task分配到不同的executor,并监控实时状态,executor开始执行任务,
- TaskScheduler收到executor执行完的信息后,表示整个应用程序完成,会向ResouceManager申请注销
spark的yarn-cluster提交流程
- client会首先向ResourceManager申请资源,要求启动AppMaster进程
- ResouceManager会分配一个节点,由NodeManager来运行AppMaster,并在AppMaster所在节点运行Driver进程 Driver进程的作用:,初始化作业,解析程序,初始化两个DAGScheduler,TaskScheduler. – 初始化作业: 判断路径是否存在,权限校验等 – DAGScheduler将程序的执行流程解析成DAG图,并划分阶段,根据阶段内的分区初始化Task – TaskScheduler接收Task,等待分配Task给executor
- AppMaster会向ResourceManager申请整个程序所需要的其他资源,准备运行executor进程
- 在各个节点上运行的executor会向Driver进行反向注册,要求分配任务
- TaskScheduler将Task分配到不同的executor,并监控实时状态,executor开始执行任务,
- TaskScheduler收到executor执行完的信息后,表示整个应用程序完成,会向ResouceManager申请注销
我们使用spark-submit(spark-sql,spark-shell我们都可以看做是spark-submit,这个两个脚本底层就是调用了spark-submit脚本)提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动(client模式),也可能在集群中某个工作节点上启动(cluster模式)。Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU core。而Driver进程要做的第一件事情,就是向集群管理器申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的memory和CPU core。 资源参数调优 以下参数就是Spark中主要的资源参数,每个参数都对应着作业运行原理中的某个部分,我这里也只能结合公司目前的情况给出一个相对靠谱的参数设置(这个不是绝对的,需要根据不同作业情况调整) num-executors 参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的Executor进程,此时你的Spark作业的运行速度是非常慢的。 参数调优建议:每个Spark作业的运行一般设置50100个左右的Executor进程比较合适,当然数据量不大的情况下设置2050个也是可以的,设置太少或太多的Executor进程都不行。设置的太少,无法充分利用集群资源;设置的太多的话,很可能会充分考验运维能力,再多的话yarn无法满足程序会挂掉。 executor-memory 参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。 参数调优建议:根据公司线上情况,每个Executor进程的内存设置4G较为合适。具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作业申请到的总内存量(也就是所有Executor进程的内存总和),这个量是不能超过队列的最大内存量的(这个量可以上Hadoop Yarn的界面查询)。此外,如果跟团队里其他人共享这个资源队列,那么申请的总内存量最好不要超过资源队列最大总内存的1/3,避免你自己的Spark作业占用了队列过多的资源,导致别的同事的作业无法运行。 executor-cores 参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。 参数调优建议:根据公司集群规模Executor的CPU core数量设置为2~3个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/3左右比较合适,也是避免影响其他同事的作业运行 driver-memory 参数说明:该参数用于设置Driver进程的内存。 参数调优建议:默认为1G,如果使用spark-sql没有去写一些特别特别特别特别复杂的sql,我个人认为这个参数可以不调!!一般这个参数是因为stage(一个shuffle就会分出一个stage)过多才需要调整,按照现有的业务逻辑基本是不需要的
PS : 一般Spark作业这4个参数是最最重要的参数,也是最容易理解的,最直观可以看出性能差距的。 其他还有一些参数比如spark.storage.memoryFraction, spark.shuffle.memoryFraction等等,这些参数是关于Spark内存管理调优,Spark 2.x以后内存管理的模型已经改成统一管理模型(与上图有些出入),对这些参数的调优有些淡化,但是如果针对某个业务场景程序员非常了解其execution与cache之间的比例,适当调整参数也可以起到意想不到的性能提升!!