Spark笔记

Spark 系统架构

  • 应用程序(Application): 基于Spark的用户程序,包含了一个Driver Program 和集群中多个的Executor;
  • 驱动(Driver): 运行Application的main()函数并且创建SparkContext;
  • 执行单元(Executor): 是为某Application运行在Worker Node上的一个进程,该进程负责运行Task,并且负责将数据存在内存或者磁盘上,每个Application都有各自独立的Executors;
  • 集群管理程序(Cluster Manager): 在集群上获取资源的外部服务(例如:Local、Standalone、Mesos或Yarn等集群管理系统);
  • 操作(Operation): 作用于RDD的各种操作分为Transformation和Action.

1.节点

整个 Spark 集群中,分为 Master 节点与 worker 节点

 Master 节点

     常驻 Master 守护进程和 Driver 进程, Master 负责将串行任务变成可并行执行的任务集Tasks, 同时还负责出错问题处理等。

 Worker 节点

       常驻 Worker 守护进程, Master 节点与 Worker 节点分工不同, Master 负载管理全部的 Worker 节点,而 Worker 节点负责执行任务. 
  每个 Worker 上存在一个或多个 Executor 进程,该对象拥有一个线程池,每个线程负责一个 Task 任务的执行.根据 Executor 上 CPU-core 的数量,其每个时间可以并行多个 跟 core 一样数量的 Task.Task 任务即为具体执行的 Spark 程序的任务. 

2.进程

Spark在执行每个Application的过程中会启动Driver和Executor两种JVM进程

  • 运行在Master节点的Driver进程为Application主控进程,即运行Application的main()函数创建和关闭SparkContext,提交Job。
  • 运行在Worker节点的Executor进程负责执行Task,并将结果返回给Driver,同时为需要缓存的RDD提供存储功能,将数据存在内存或者磁盘上,每个Application都有各自独立的Executors

3.专业术语

Job
一个Application可以产生多个Job,其中Job由Spark Action触发产生。每个Job包含多个Task组成的并行计算
Stage

每个Job会拆分为多个Task,作为一个TaskSet,称为Stage;Stage的划分和调度是由DAGScheduler负责的。

.Stage划分算法思想

(1)一个Job由多个Stage构成

一个Job可以有一个或者多个Stage,Stage划分的依据就是宽依赖,产生宽依赖的算子:reduceByKey、groupByKey等等

(2)根据依赖关系,从前往后依次执行多个Stage

SparkApplication 中可以因为不同的Action触发众多的Job,也就是说一个Application中可以有很多的Job,每个Job是有一个或者多个Stage构成,后面的Stage依赖前面的Stage,也就是说只有前面的Stage计算完后,后面的Stage才会运行。

(3)Stage的执行时Lazy级别的

所有的Stage会形成一个DAG(有向无环图),由于RDD的Lazy特性,导致Stage也是Lazy级别的,只有遇到了Action才会真正发生作业的执行,在Action之前,Spark框架只是将要进行的计算记录下来,并没有真的执行。


Stage分为Result Stage和Shuffle Map Stage;

 

举个例子

通过一个例子,详细分析整个划分DAG流程。下面代码是Wordcount的变形,这么处理是为了让代码简单的同时,Stage也足够丰富。

    val sc = new SparkContext("local","wordcount")
    val data = sc.parallelize(List("a c", "a b", "b c", "b d", "c d"), 2)
    val wordcount = data.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).map(x => (x._2, x._1)).reduceByKey(_ + _)

    val data2 = sc.parallelize(List("a c", "a b", "b c", "b d", "c d"), 2)
    val wordcount2 = data2.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).map(x => (x._2, x._1)).reduceByKey(_ + _)

    wordcount.join(wordcount2).collect()

打印出上述代码中RDD的依赖关系 
这里写图片描述 
整理如下 
这里写图片描述

  1. 最左一列的parallelizemap等表示实例代码中的transformation。
  2. 圆角矩形表示transformation操作生成的RDD和该RDD的Dependency,其中ShuffleDependency使用蓝色标注。

在上图ShuffleDependency处切分DAG生成Stage,结果如下 
这里写图片描述

  1. 圆角矩形代表Stage,结果为四个ShuffleMapStage ,一个ResultStage。
  2. 圆角矩形内为Stage的两个属性。ShuffleMapStage和ResultStage有差别。

到这里,Stage就划分完成了,最后贴张spark webUI的图片 
这里写图片描述

Task
Application的运行基本单位,Executor上的工作单元。其调度和 管理又TaskScheduler负责。

对一个Stage之内的RDD进行串行操作的计算任务。每个Stage由一组并发的Task组成(即TaskSet),这些Task的执行逻辑完全相同,只是作用于不同的Partition。一个Stage的总Task的个数由Stage中最后的一个RDD的Partition的个数决定。

Spark Driver会根据数据所在的位置分配计算任务,即把所有Task根据其Partition所在的位置分配给相应的Executor,以尽量减少数据的网络传输(这也就是所谓的移动数据不如移动计算)。一个Executor内同一时刻可以并行执行的Task数由总CPU数/每个Task占用的CPU数决定,即spark.executor.cores / spark.task.cpus

Task分为ShuffleMapTask和ResultTask两种,位于最后一个Stage的Task为ResultTask,其他阶段的属于ShuffleMapTask。


Persist & Checkpoint

Persist

通过RDD的persist方法,可以将RDD的分区数据持久化在内存或硬盘中,通过cache方法则是缓存到内存。这里的persist和cache是一样的机制,只不过cache是使用默认的MEMORY_ONLY的存储级别对RDD进行persist,故“缓存”也就是一种“持久化”。
前面提到,只有触发了一个Action之后,Spark才会提交Job进行真正的计算。所以RDD只有经过一次Action之后,才能将RDD持久化,然后在Job间共享,即如果两个Job用到了相同的RDD,那么可以在第一个Job中对这个RDD进行缓存,在第二个Job中就避免了RDD的重新计算。持久化机制使需要访问重复数据的Application运行地更快,是能够提升Spark运算速度的一个重要功能。

Checkpoint

调用RDD的checkpoint方法,可以将RDD保存到外部存储中,如硬盘或HDFS。Spark引入checkpoint机制,是因为持久化的RDD的数据有可能丢失或被替换,checkpoint可以在这时候发挥作用,避免重新计算。
创建checkpoint是在当前Job完成后,由另外一个专门的Job完成:

也就是说需要checkpoint的RDD会被计算两次。因此,在使用rdd.checkpoint()的时候,建议加上rdd.cache(),这样第二次运行的Job久不用再去计算该rdd了。

一个Job在开始处理RDD的Partition时,或者更准确点说,在Executor中运行的任务在获取Partition数据时,会先判断是否被持久化,在没有命中时再判断是否保存了checkpoint,如果没有读取到则会重新计算该Partition。


Partition(分区)

一个RDD在物理上被切分为多个Partition,即数据分区,这些Partition可以分布在不同的节点上。Partition是Spark计算任务的基本处理单位,决定了并行计算的粒度,而Partition中的每一条Record为基本处理对象。例如对某个RDD进行map操作,在具体执行时是由多个并行的Task对各自分区的每一条记录进行map映射。



工作流程

1.        提交Application

如果客户端选择由Worker运行Driver,则客户端首先会向Master提交Application。提交的过程是通过客户端调用org.apache.spark.deploy.Client类来完成的。

2.        Master调度应用

成功提交后应用,Master调度应用,针对每个应用分发给指定的一个Worker来启动Driver,并且Master给Worker指令启动Executor,之后Executor向Driver内的SchedulerBackend注册。Master调度应用的方法是FIFO,从第一台worker开始,从等待被运行的Driver的FIFO队列里依次取出符合这个worker资源限制的Driver,分配给这个Worker。直到Driver被分配完或者worker被遍历完。如果Driver是在客户端,启动后,会向Master注册。

3.        Driver启动过程

Driver端启动后(可能是client端自已启动,也可能是由Master分配到worker上启动),创建DriverRunner线程,在DriverRunner线程内创建SchedulerBackend进程。这个进程会等待分配了任务的属于该应用的Executor的注册。SchedulerBackend进程维护了DAGScheduler组件,DAGScheduler用于根据RDD的DAG切分Stage,生成TaskSet,TaskSet被存放到TaskScheduler中,并由它执行调度和分发Task到Executor的操作。

在运行用户程序的过程中,当遇到Action算子时,实际上是由spark库代码来调用SparkContext的runJob来提交一个Job。

4.        Job的切分

Job的切分由DAGScheduler完成。DAGScheduler根据每个Job的对应RDD DAG切分出Stage DAG。在这个Stage DAG上通过广度优先遍历,找到最上游的Stage。DAGScheduler组件本身维护了三个Stage集合(等待执行的、正在执行的、执行失败的)来帮助决定Stage的被调度顺序。

5.        Stage的调度

Stage也是以FIFO的方式调度的。实际在代码中,每个Job会先被DAGScheduler切分成多个Stage,并根据Stage的依赖顺序,将依赖上游的Stage放到调度池中。每个Stage对象中包含两个关于优先级的信息:1.Job Id(先比较这个,越小优先级越高);2.Stage Id(再比较这个,也是越小越高)。越先提交的Job,其Stage中的Job Id越小,所以先提交的Job会先被调度;同一Job中越靠上游的Stage Id越大,但DAGScheduler能保证把上游的Stage先放入调度池中的。所以TaskSetManager只要取出调度池中优先级高的Stage(优先级的是通过以上所说的两两比较Job Id和Stage Id而得出的)并满足资源限制的Stage进行调度即可。

另外,Job的调度策略和调度细节,也可以配置,这里不作说明,见相关文档。这里说的是Stage的调度,而实际上代码中被调度的应该是Stage中的TaskSetManager对象。Stage、TaskSetManager、TaskSet在代码层面是三个东西,而从概念上理解可以认为是一个东西。

6.        Task的调度

Task调度的策略由TaskSetManager决定,而实际的分发操作由TaskScheduler完成。Task被调度的基本策略就是:如果RDD调用过cache,则这个Task被分发到分区缓存所在机器的Executor上执行;直接获取RDD中的执行点,所谓执行点,一个例子就是HDFS读出一个块数据所在的机器;如果Stage是窄依赖,则直接把Task给Stage内最上游RDD的各个分区所在机器。

7.        Executor的执行

Master给Worker指令启动Executor。Worker创建Executor的过程是:先创建Executor-Runner线程,这个线程又启动ExecutorBackend进程。Executor向Driver内的SchedulerBackend注册。并Executor之后等待Driver分配Task,获取Task后执行。



Standalone部署方式下某一节点出现问题时,系统如何处理?
出现问题的节点可能发生的情况有三种:
(1)Master崩掉了:这个坏掉了,就真的没法完了。单点故障的问题。
有两种解决办法:第一种基于文件系统的故障恢复,适合Master进程本身挂掉,那直接重启就Ok了。
第二种是基于ZookerKeep的HA方式。此方式被许多的分布式框架使用。
(2)某一Worker崩掉了:
若是所有的Worker挂掉,则整个集群就不可用;
Worker退出之前,会将管控的所有Executor进程kill;由于Worker挂掉,不能向master玩心跳了,根据超时处理会知道Worker挂了,然后Master将相应的情况汇报给Driver。Driver会根据master的信息和没有收到Executor的StatusUpdate确定这个Worker挂了,则Driver会将这个注册的Executor移除。
(3)某Worker的Excutor崩掉了:
Excutor的作为一个独立的进程在运行,由ExcutorRunner线程启动,并收到ExcutorRunner的监控,当Excutor挂了,ExcutorRunner会注意到异常情况,将ExecutorStateChanged汇报给Master,master会再次发送lanchExecutor指令给相应的Worker启动相应的Excutor。

driver & worker | center 5

2.1 spark 运行原理

一开始看不懂的话可以看完第三和第四章再回来看. 
底层详细细节介绍6: 
  我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。而Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的资源管理集群,美团•大众点评使用的是YARN作为资源管理集群)申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。 
  在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批Task,然后将这些Task分配到各个Executor进程中执行。Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个Task处理的数据不同而已。一个stage的所有Task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的Task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。 
  Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个Task可能都会从上一个stage的Task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。 
  当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个Task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。 
  因此Executor的内存主要分为三块:第一块是让Task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让Task通过shuffle过程拉取了上一个stage的Task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。 
  Task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个Task,都是以每个Task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的Task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些Task线程。 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值