1. Spark内核架构
1.1 spark runtime 流程示意图
1.2 driver、SparkContextspark、executor等概念
Executor是运行在Worker节点上的为当前应用程序而开启的一个进程里面的处理对象,这个对象负责了Task的运行,通过线程池中的线程并发执行和线程复用的方式,线程池中的每一个线程可以运行一个任务,任务完成后回收到线程池中进行线程复用。
application = driver + executor
SparkContext里有高层调度器、低层调度器、SchedulerBackend。高层调度器就是把整个作业划分成几个阶段,底层调度器是每个阶段里的任务该如何处理,SchedulerBackend是管理整个集群中为当前程序分配的计算资源(Executor)。
SparkContext在创建这些对象的同时会向Master注册当前程序,注册OK后会分配资源,根据Action触发的JOB,JOB里有RDD,从后往前推,如果有宽依赖的话,就划分成不同的Stage(一个Stage内部都是计算轮回完全一样,只是计算的数据不同而已)。stage划分完成后提交给底层调度器TaskScheduler。TaskScheduler拿到任务的集合,就会根据数据的本地性把任务发到Executor执行。
Executor在出问题时会向Driver汇报。运行完后SparkContext会关闭。当然创建的对象也都会关闭。
Driver 是驱动 Executor 工作的,Executor是具体处理数据分片的,内部是线程池并发地处理数据分片。 应用程序是Driver和
Executors的模式,每个应用程序都有Executor代码。
Executor部分代码:main方法中new SparkConf 以及一些操作方法
driver部分的代码:SparkConf进行配置然后创建SparkContext,SparkConf+SparkContext,如下:
driver部分代码:Sparkconf + SparkContext
val conf =new SparkConf()
conf.setAppName("...")
conf.setMaster("...")
val sc = new SparkContext(conf)
SparkContext创建的过程中做了很多内容,包括DAG Scheduler(一个应用程序默认只有一个DAG Scheduler)、TaskScheduler、SchedulerBackend、SparkEnv。
textFile、flatMap、map、reduceByKey:这些代码都是Transformation级别的,都会产生RDD,既是RDD操作又会产生RDD,这些代码就是具体的业务实现,就是Executor中具体执行的代码。最后都会被Action触发执行,都是在Worker中的Executor中处理的。
注:driver是以sparkContext为核心的,可以理解为driver就是sparkContext
1.3 spark cluster 流程示意图
Executor是运行在Worker节点上的为当前应用程序而开启的一个进程里面的处理对象,这个对象负责了Task的运行,通过线程池中的线程并发执行和线程复用的方式,线程池中的每一个线程可以运行一个任务,任务完成后回收到线程池中进行线程复用。
Executor就是进程里的对象。默认情况下Driver运行在当前提交的机器上,一个worker默认分为当前的应用程序只开启一个executor,当然可以配置为多个。
※ 问题:一个worker里executor是多点好还是少点好?
=>看情况。如果只分了一个Executor,占据了大量CPU core,但资源闲置,造成资源浪费。
由于CPU Cores的个数是有限的,如果只开启一个Executor,当任务比较大时内存易OOM。这时最好分成几个不同的Executor。
ClusterManager是集群获取资源的外部服务。Spark最开始时没有Yarn模式,也没有Standalone模式,最开始的资源管理服务是mesos。Spark程序的运行不依赖于ClusterManager。 Spark应用程序向clustermanager注册成功后,Master就提前直接分配好资源,程序运行过程中不需要ClusterManager参与。( ClusterManager是可插拨的,这种资源分配方式是粗粒度的资源分配方式)
worker就是集群中任何可以运行APP具体操作代码的节点。worker上不会运行程序代码,worker是管理当前节点内存CPU等资源使用状况的,它会接收Master分配资源的指令,并通过executor runner启动一个新进程,进程内有Executor。
管理很多worker,worker是工头,Worker下面有很多资源。
※ worker会不会向master汇报当前node的资源信息?
=> 不会。 worker会不断向Master发的心跳,但内容只有worker ID。是用来判断Worker是否活着。
那master怎么知道各节点的资源信息?
=> 分配资源的时候就已经知道了。应用程序在向Master注册时,注册成功后master就会分配资源,分配时就会记录资源,所有的资源都是Master分配的,所以Master当然知道各节点的资源信息了。
只有当Worker出现故障时才会向Master汇报资源情况。
JOB
JOB就是包含了一系列Task的并行计算。JOB一般由应用程序的Action操作触发,比如saveAsTextFile。
JOB里面是一系列的RDD及作用在RDD的各种operation操作,collect就是一个Action,会触发一个作业。wordCountOrdered就是一系列的RDD及对RDD的操作。包括map、flatMap、TextFile,每一步都会至少产生一个RDD。TextFile就会产生hadoopRDD 和MapPartitionsRDD。
JOB都是由Action触发的,触发时前面有一系列的RDD。
action不会产生RDD,只会导致RunJOB。
action前是RDD,是transformation级别的,是lazy级别的执行方式。
如果后面的RDD对前面的RDD进行回溯时是窄依赖的话,就会在内存中进行迭代,这是Spark快的一个很重要的原因。
spark快不仅是因为基于内存。调度,容错才是Spark的精髓的基本点。
窄依赖有一个Range级别,即依赖固定个数的父RDD。所谓固定个数是说不会随着数据规模的扩大而改变。
依赖构成了DAG。如果是宽依赖DAG Scheduler就会划分Stage,Stage内部是基于内存迭代的,当然也可以基于磁盘迭代。
stage内部计算逻辑完全一样,只是计算的数据不同。
任务(Task)就是计算一个数据分片的,数据分片:例如从HDFS上读取数据时默认数据分片就是128MB。
※ 一个数据分片是否精准地等于一个Block的大小(默认128MB)?
=> 一般情况下都不等于,因为最后一个分片会跨两个Block。
1.4 通过spark historyserver UI查看
http://master-1a:18080
问题:没有发现DAG visualization界面,待解决。
2. SparkDAG概念
在spark里每一个操作生成一个RDD,RDD之间连一条边,最后这些RDD和他们之间的边组成一个有向无环图,这个就是DAG。
Spark内核会在需要计算发生的时刻绘制一张关于计算路径的有向无环图,也就是DAG。
有了计算的DAG图,Spark内核下一步的任务就是根据DAG图将计算划分成任务集,也就是Stage,这样可以将任务提交到计算节点进行真正的计算。Spark计算的中间结果默认是保存在内存中的,Spark在划分Stage的时候会充分考虑在分布式计算中可流水线计算(pipeline)的部分来提高计算的效率,而在这个过程中,主要的根据就是RDD的依赖类型。根据不同的transformation操作,RDD的依赖可以分为窄依赖(Narrow Dependency)和宽依赖(Wide Dependency,在代码中为ShuffleDependency)两种类型。窄依赖指的是生成的RDD中每个partition只依赖于父RDD(s) 固定的partition。宽依赖指的是生成的RDD的每一个partition都依赖于父 RDD(s) 所有partition。窄依赖典型的操作有map, filter, union等,宽依赖典型的操作有groupByKey, sortByKey等。可以看到,宽依赖往往意味着shuffle操作,这也是Spark划分stage的主要边界。对于窄依赖,Spark会将其尽量划分在同一个stage中,因为它们可以进行流水线计算。
至于以上一些shuffer过程建议大家多看看spark官网会比较好点。