文章目录
- 一.spark的部署模式
- 二.driver的功能
- 三.hadoop和spark都是并行计算,他们有什么相同点和不同点
- 四.RDD
- 五.简述宽依赖和窄依赖概念,groupByKey,reduceByKey,map,filter,union都是什么依赖?
- 六.spark如何防止内存溢出
- 七.stage task 和 job的区别和划分方式
- 八.spark提交作业参数
- 九.reduceByKey和groupByKey的区别
- 十.foreach和map的区别
- 十一.map和mapPartitions的区别
- 十二.foreach和foreachPartition的区别
- 十三.sortByKey是全局排序吗?
- 十四.coalesce与repartition的区别
- 十五.spark血统-即RDD之间依赖关系
- 十六.spark RDD 的持久化
- 十七.spark提交任务流程
- 十八.spark join的优化
- 十九.spark 的shuffle方式
- 二十.广播变量作用
- 廿一.数据倾斜解决方案
- 廿二.spark通信机制
一.spark的部署模式
1.本地模式
- spark不一定非要跑在hadoop集群,可以在本地,起多个线程的方式来指定.将spark应用以多线程的方式直接运行在本地,一般都是为了方便调试,本地模式分为三类
- 1)local:只启动一个executor
- 2)local[k]:启动k个executor
- 3)local[*]:启动跟cpu数目相同的executor
2.standalone模式
- 分布式部署集群,自带完整的服务,资源管理和任务监控是spark自己监控,这个模式也是其他模式的基础
3.spark on yarn模式
- 分布式部署集群,资源和任务监控交给yarn管理,spark客户端直接连接yarn不需要格外构建spark集群.有yarn-client和yarn-cluster两种模式,主要区别在于:Driver程序的运行节点
- 1)cluster适合生产,driver运行在集群子节点,具有容错功能
- 2)client适合调试,driver运行在客户端
二.driver的功能
- 一个spark作业运行时包括一个Driver进程,也是作业的主进程,具有main函数,并且具有SparkContext的实例,是程序的入口点
- 功能:负责向集群申请资源,向master注册信息,负责了作业的调度,负责作业的解析,生成stage并调度Task到Executor上.包括DAGScheduler,TaskScheduler
三.hadoop和spark都是并行计算,他们有什么相同点和不同点
- 两者都是用mr模型来进行并行计算,hadoop的一个作业称为job,job里面分为map task和reduce task,每个task都是在自己的进程中运行的,当task结束时,进程也会结束
- spark用户提交的任务称为application,一个application对应一个SparkContext,app中存在多个job,每触发一次action操作就会产生一个job.这些job可以并行或串行执行,每个job中有多个stage,stage是shuffle过程中DAGScheduler通过RDD之间的依赖关系划分job而来的,每个stage里面有多个task,组成taskset有TaskScheduler分发到各个executor中执行,executor的生命周期和app是一样的,即使没有job运行也是存在的,所以task可以快速启动读取内存进行计算,spark的迭代计算都是在内存中进行的,API提供了大量的RDD操作如joinmgroupby等,而且通过DAG图可以实现良好的容错
- hadoop的job只有map和reduce操作,表达能力比较欠缺而且在mr过程中会重复的读写hdfs,造成大量的io操作,多个job需要自己管理关系
四.RDD
- RDD(resilient distributed dataset)叫做弹性分布式数据集,是spark中最基本的数据抽象,它代表一个不可变,可分区,里面的元素可进行计算的集合
RDD五大特性 - 1)A list of paritions 一个分区列表,RDD中的数据都存在一个分区列表里面
- 2)A function for computing each split 作用在每一个分区中的函数
- 3)A list of dependencies on the RDDs 一个RDD依赖于其他多个RDD,这个点很重要,RDD的容错机制就是根据这个特性而来的
- 4)Optionally ,a Partitioner for key-value RDDs(e.g. to say that the RDD is hash-partitioned) 可选的,针对于kv类型的RDD才具有这个特性,作用是决定了数据的来源以及数据处理后的去向
- 5)Optionally,a list of preferred locations to compute each split on(e.g. block locations for an HDFS file) 可选项,数据本地性,数据位置最优
五.简述宽依赖和窄依赖概念,groupByKey,reduceByKey,map,filter,union都是什么依赖?
1.窄依赖
- 指父RDD的每一个分区最多被一个子RDD的分区所用,表现为一个父RDD的分区对应于一个子RDD的分区,和两个父RDD的分区对应于一个RDD的分区.map/filter和union属于第一类,对输入进行协同划分(co-partitioned)的join属于第二类
2.宽依赖
- 指子RDD的分区依赖于父RDD的所有分区,这是因为shuffle类操作
算子的宽窄依赖
- 对RDD进行map,filter,union等Transformations一般是窄依赖
- 宽依赖一般是对RDD进行groupByKey,reduceByKey等操作,就是对RDD中的partition中的数据进行重分区(shuffle)
- join操作既可能是宽依赖也可能是窄依赖,当要对RDD进行join操作时,如果RDD进行过重分区则为窄依赖,否则为宽依赖
六.spark如何防止内存溢出
1.driver端的内存溢出
- 可以增大driver的内存参数:
spark.driver.memory(default 1g)
- 这个参数用来设置driver的内存.在spark程序中,SparkContext,DAGScheduler都是运行在driver端的.对应rdd的Stage切分也是在Driver端运行,如果用户自己写的程序有过多的步骤,切分出过多的Stage,这部分信息消耗的是driver的内存,这个时候就需要调大driver的内存
2.map过程产生大量对象导致内存溢出
- 这种溢出的原因是在单个map中产生了大量的对象导致的,例如:rdd.map(x=>for(i<- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这肯定很容易产生内存溢出的问题.针对这种问题,在不增加内存的情况下,可以通过减少每个task的大小,以便达到每个task即使产生大量的对象Executor的内存也能够装得下.具体做法可以在会产生大量的对象的map操作之前调用repartition方法,分区成更小的块传入map.例如:rdd.repartition(10000).map(x=>for(i<- 1 to 10000) yield i.toString)
- 面对这种问题注意,不能使用rdd.coalesce方法,这个方法只能减少分区,不能增加分区,不会有shuffle过程
3.数据倾斜导致内存溢出
- 数据不平衡除了有可能导致内存溢出外,也有可能导致性能问题,解决方法和上面说的类似,就是调用repartition重新分区
4.shuffle后内存溢出
- shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的.在spark中,join,reduceByKey这一类型的过程都会有shuffle过程,在shuffle的使用,需要传入一个partitioner,大部分spark中的shuffle操作,默认的partitioner都是HashPartitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用saprk.sql.shuffle.partitions),该参数只对HashPartitioner有效,所以如果是别的Partitioner或者自己实现的Partitioner就不能用该参数来控制shuffle的并发量.如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量
5.standalone模式下资源分配不均导致内存溢出
- 在standalone的模式下如果配置了
--total-executor-cores
和--executor-memory
这两个参数,但是没有配置--executor-cores
这个参数的话,就有可能导致每个Executor的memory是一样的,但是cores的数量不同,那么在cores数量多的executor中,由于能够同时执行多个task,就容易导致内存溢出的情况.这种情况的解决方法就是同时配置--executor-cores
或者spark.executor.cores
参数,确保executor资源分配均匀
6.使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache()
- rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等价的,在内存不足的时候rdd.cache()的数据会丢失,再次使用的时候会重算,而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在内存不足的时候会存储在磁盘,避免重算,只是消耗点IO时间
七.stage task 和 job的区别和划分方式
- job:一个由多个任务组成的并行计算,当你需要执行一个rdd的action时,会生成一个job
- stage:每个job被拆分成更小的被称作stage(阶段)的task(任务)组,stage彼此之间是相互依赖的,各个stage会按照执行顺序依次执行
- task:一个将要被发送到executor中的工作单元,是stage的一个任务执行单元,一般来说,一个rdd有多少个partition,就会有多少个task,因为每一个task只是处理一个partition上的数据
八.spark提交作业参数
executors-cores
------每个executor使用的内核数,默认为1,官方建议为2-5个num-executors
------启动executors的数量,默认为2executor-memory
------executor内存大小,默认1Gdriver-cores
------driver使用的内核数,默认为1driver-memory
------driver内存大小,默认512M
#如下是一个提交任务的样式:
spark-submit \
--master local[5] \
--driver-cores 2 \
--driver-memory 8g \
--executor-cores 4 \
--num-executors 10 \
--executor-memory 8g \
--class PackageName.ClassName XXXX.jar \
--name "spark job name" \
InputPath \
OutputPath
九.reduceByKey和groupByKey的区别
- reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义
- groupByKey也是对每个key对应的多个value进行操作,但是只是汇总生成一个sequence,本身不能自定义函数,只能额外通过map(func)来实现
- 在大的数据集上,reduceByKey的效果比groupByKey的效果更好些,因为reduceBykey会在shuffle之前对数据进行合并,传输速度优于groupByKey
- combineByKey,是一个比较底层的算子,reduceByKey就调用了该算子
十.foreach和map的区别
- 两个方法的共同点:都是用于遍历集合对象,并对每一项执行指定的方法
- 两者的区别:
- 1)foreach无返回值(准确来说返回Unit),map返回集合对象.foreach用于遍历集合,而map在于映射集合到另一个集合
- 2)foreach中的处理逻辑是串行的,map中的处理.逻辑是并行的
- 3)map是转换算子,foreach是行动算子
十一.map和mapPartitions的区别
相同点:map与mapPartitions都属于转换算子
区别:
- 1.本质
- 1)map是对rdd中每一个元素进行操作
- 2)mapPartitions则是对rdd中的每个分区的迭代器进行操作
- 2.RDD中每个分区数量不大的情形
- 1)map操作性能地下,比如一个partition中有一万条数据,那么在分析每个分区时,function要执行和计算一万次
- 2)mapPartitions性能较高,使用mapPartitions操作之后,一个task仅仅会执行一次function.function一次接收所有的partition数据,只要执行一次就可以了,性能比较高
- 3.RDD中的每个分区数据量超大的情形:比如一个Partition有100万条数据
- 1)map能正常执行完
- 2)mapPartitions一次传入一个function后,可能一下子内存不够用,造成OOM(内存溢出)
十二.foreach和foreachPartition的区别
相同点:foreach和foreachPartition都属于行动算子
区别:
- 1)foreach每次处理RDD中的一条数据
- 2)foreachPartition每次处理RDD中的每个分区的迭代器中的数据
十三.sortByKey是全局排序吗?
sortByKey是全局排序
- 1)在sortByKey之前将数据使用partitioner根据数据范围来分
- 2)使得p1分区所有的数据小于p2,p2分区所有的数据小于p3,以此类推(p1-pn是分区标识)
- 3)然后使用sortByKey算子针对每一个Partition进行排序,这样全局的数据就被排序了
十四.coalesce与repartition的区别
- 我们常认为coalesce不产生shuffle回避repartition产生shuffle效率高,而实际情况往往要根据具体问题具体分析,coalesce效率不一定高,有时还有大坑,要慎用此算子
- coalesce与repartition都是对RDD的分区进行重新划分,repartition只是coalesce接口中shuffle为true的实现
例子详解
- 假设源RDD有N个分区,需要重新划分为M个分区
- 如果N<M.一般情况下N个分区有数据分布不均匀的状况.利用HashPartitioner函数将数据重新分区为M个,这时需要将shuffle设置为true(repartition实现,coalesce也实现不了)
- 如果N>M并且和M相差不多,(假如N为1000,M为100)那么就可以将N个分区中的若干个分区合并成一个新的分区,最终合并成为M个分区,这时可以将shuffle设置为false(coalesce实现),如果M>N时,coalesce是无效的,不进行shuffle过程,父RDD和子RDD之间是窄依赖关系,无法使文件数(partition)变多,总之如果shuffle为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的
- 如果N>M并且两者相差悬殊,这时要看executor数与要生成的partitions关系,如果executor数<=要生成partition数,coalesce效率高,反之如果用coalesce会导致(executor数-要生成partition数)个executor空跑从而降低效率.如果在M为1的时候,为了使coalesce之前的操作有更好的并行度,可以将shuffle设置为true
十五.spark血统-即RDD之间依赖关系
- 处理分布式运算环境下的数据容错性(节点失效/数据丢失)问题采用的方案.为了保证RDD中数据的鲁棒性(也叫健壮性),RDD数据集通过所谓的血统关系(lineage)记住了它是如何从其他RDD中演变过来的.相比其他系统的细颗粒度的内存数据更新级别的备份或者LOG机制,RDD的lineage记录的是粗颗粒度的特定数据转换(Transformation)操作行为.当这个RDD的部分分区数据丢失时,它可以通过lineage获取足够的信息来重新运算和恢复丢失的数据分区.这种粗颗粒的数据模型,限制了spark的运用场合,但同时相比细颗粒度的数据模型,也带来了性能的提升
- RDD在lineage依赖方面分为两种:窄依赖与宽依赖,用来解决数据容错时的高效性
- **窄依赖:**是指父RDD的每一个分区最多被一个子RDD的分区对应一个子RDD的分区,表现为一个父RDD的的分区对应于一个子RDD的分区,或多个父RDD的分区对应一个子RDD的分区,也就是说一个父RDD的一个分区不可能对应一个子RDD的多个分区
- **宽依赖:**是指子RDD的分区依赖于父RDD的多个分区或所有分区,也就是说存在一个父RDD的一个分区对应一个子RDD的多个分区
- 对于宽依赖,这种计算的输入和输出在不同的节点,lineage方法对于输入节点完好,而输出节点宕机时,通过重新计算这种情况下这种方法是有效的,否则无效,因为无需重试,需要向上其祖先追溯看是否可以重试(这就是lineage,血统的意思),窄依赖对于数据的重算开销要远小于宽依赖的数据重算开销
- 在RDD计算,通过checkpoint进行容错,做checkpoint有两种方式,一个是checkpoint data,一个是logging the updates.用户可以控制采用哪种方式来实现容错,默认是logging the updates方式,通过记录跟踪所有生成RDD的转换,也就是记录每个RDD的血统来重新计算生成丢失的分区数据
十六.spark RDD 的持久化
1.cache() 和 persist()
- 当对RDD执行持久化操作时,每个节点都会将自己操作的RDD的partition持久化到内存中,并且在之后对该RDD的反复使用中,直接使用内存缓存的partition,这样的话,对于针对一个RDD反复执行多个操作的场景,就只要对RDD计算一次即可,后面直接使用该RDD,而不需要计算多次该RDD
- 巧妙使用RDD持久化,甚至在某些场景下,可以将spark应用程序的性能提升10倍,对于迭代式算法和快速交互式应用来说,RDD持久化是非常重要的
- 要持久化一个RDD,只要调用其cache()或者persist()方法即可.在该RDD第一次被计算出来时,就会直接换存在每个节点中,而且spark的持久化机制还是自动容错的.如果持久化的RDD的任何partition丢失了,那么spark会自动通过其源RDD使用转换操作重新计算该partition
- cache()和persist()的区别在于:cache是persist的一种简化方式,cache的底层就是调用的persist的无参版本,同时就是调用persist(MEMORY_ONLY),将数据持久化到内存中.如果需要从内存中去除缓存,那么就可以调用unpersist方法
2.checkPoint
场景:
- 当业务场景非常复杂的时候,RDD的lineage依赖会非常的长,一旦血统较后的RDD数据丢失时,spark会根据血统依赖重新计算丢失的RDD,这样会造成计算的时间过长,spark提供了一个叫checkPoint的算子来解决这样的业务场景
使用:
- 为当前RDD设置检查点,该函数会创建一个二进制文件,并存储到checkPoint目录中,该目录是使用SparkContext.setCheckpointDir()设置的.在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除.对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发
checkPoint的优点:
- 持久化在hdfs上,hdfs默认的3副本备份使得持久化的备份数据更加安全
- 切断RDD的依赖关系:当业务场景复杂的时候,RDD的依赖关系非常的长的时候,当靠后的RDD数据丢失的时候,会经历较长的重新计算的过程,采用checkPint会转为依赖checkPointRDD,可以避免长的lineage重新计算
- 建议checkpoint之前进行cache操作,这样会直接将内存中的结果进行checkPoint,不用重新启动job重新计算
checkPoint原理:
- 当finalRDD执行Action类算子计算job任务的时候,spark会从finalRDD从后往前回溯查看哪些RDD使用了checkPoint算子
- 将使用了checkPoint的算子标记
- spark会自动地启动一个job来重新计算标记了的RDD,并将计算的结果存入hdfs,然后切断RDD的依赖关系
十七.spark提交任务流程
1.standalone-client方式提交任务
- 1)client模式下提交任务,在客户端启动driver进程
- 2)driver会向master申请启动application启动的资源
- 3)资源申请成功,driver端将task发送到worker端执行
- 4)worker将task执行结果返回到driver端
2.standalone-cluster方式提交任务
- 1)standalone-cluster模式提交app后,会向master请求启动driver
- 2)master接受请求后,随机在集群中一台节点启动driver进程
- 3)driver启动后为当前的应用程序申请资源
- 4)driver端发送task到worker节点上执行
- 5)worker将执行情况和执行结果返回给driver端
3.yarn-client方式提交任务
- 1)客户端提交一个application,在客户端启动一个driver进程
- 2)应用程序启动后会向RS(ResourceManager)发送请求,启动AM(ApplicationMaster)的资源
- 3)RS收到请求,随机选择一台NM启动AM,这里的NM相当于standalone中的worker节点
- 4)AM启动后,会向RS请求一批container资源,用于启动executor
- 5)RS会找到一批NM返回给AM,用于启动Executor
4.yarn-cluster方式提交任务
- 1)客户机提交application应用程序,发送请求到RS,请求启动AM
- 2)RS收到请求后随机在一台NM上启动AM(相当于driver端)
- 3)AM启动,AM发送请求到RS,请求一批container用于启动executor
- 4)RS返回一批NM节点给AM
- 5)AM连接到NM,发送请求到NM启动executor
- 6)executor反向注册到AM所在节点的driver.driver发送task到executor
十八.spark join的优化
- spark作为分布式的计算框架,最为影响其执行效率的地方就是频繁的网络传输.所以一般的在不存在数据倾斜的情况下,想要提高spark job的执行效率,就尽量减少job的shuffle过程(减少 job 的stage)或者减小shuffle带来的影响
- 1)尽量减少参与join的RDD的数据量
- 2)尽量避免参与join的RDD都具有重复的key
- 3)尽量避免或者减少shuffle过程
- 4)条件允许的情况下,使用map-join 完成join
十九.spark 的shuffle方式
shuffle方式共分三种,分别是:HashShuffle,SortShuffle(默认),TungstenShuffle
在spark程序中设置方式:通过设置spark.shuffle.manager进行配置:
// 可设置为hash sort tungsten-sort
private val session: SparkSession = SparkSession.builder()
.appName("xxx").master("local[*]").
config("spark.shuffle.manager","hash").getOrCreate()
HashShuffleManager特点:
- 1)数据不进行排序,速度较快
- 2)直接写入缓冲区,缓冲区写满后溢写为文件
- 3)本ShuffleMapStage的每一个task会生成与下一个ShuffleMapStage并行度相同的文件数量
- 4)海里文件操作句柄和临时缓存信息,占用内存容易内存溢出
SortShuffleManager的特点:
- 1)会对数据进行排序
- 2)在写入缓存之前,如果是reduceByKey之类的算子,则会先写入到一个Map内存数据结构中,而如果是join之类的算子,则先写入到Array内存数据结构中.在每条数据写入前先判断是否当到达一定阀值,到达则写入到缓冲区
- 3)复用一个core的task会写到同一个文件里,并生成一个索引文件.其中记录了下一个ShuffleMapStage中每一个task所要拉取数据的start offset 和end offset
二十.广播变量作用
- 使用广播变量,每个executor的内存中只驻留一份变量副本,而不是对每个task都传输一次大变量,省了很多的网络传输,对性能提升有很大的帮助,而且会通过高效的广播算法(比特洪流技术)来减少传输代价
- 使用广播变量的场景很多,我们都知道spark一种常见的优化方式就是小表广播,使用map join来代替reduce join,我们通过把小数据集广播到各个节点上,节省了一次特别expensive的shuffle操作
- 比如driver上有一张数据量很小的表,其他节点上的task都需要lookup这张表,那么driver可以先把这张表copy到这些节点,这样task就可以在本地查表了
廿一.数据倾斜解决方案
- 数据倾斜的发生一般都是一个key对应的数据过大,而导致task执行过慢,或者内存溢出,OOM,一般发生在shuffle的时候,比如reduceByKey,countByKey,groupByKey容易产生数据倾斜
- 如何解决数据倾斜,首先看log日志信息,因为log日志报错时会提示在哪些行,然后就是检查发生shuffle的地方,这些地方比较容易发生数据倾斜
方案一:聚合源数据
- 我们的数据一般来源于hive表,那么在生成hive表的时候对数据进行聚合,按照key进行分组,将key对应的所有values以另一种格式存储,比如拼接一个字符串这样的话,可以省略groupByKey和reduceByKey的操作,那么没有这样操作的话,就不用shuffle了,没有shuffle的话不可能出现数据倾斜,如果不能完美拼接,但是能少量拼接也能减少key对应的数据量,也可提高性能
方案二:过滤导致倾斜的key
- 这种方案就是说如果业务允许或者沟通后能理解的话,我们可以把大量的key进行过滤,这样可以轻松解决问题
方案三:提高shuffle操作reduce并行度
- 通过提高reduce端的task执行数量,来分担数据压力,也就是说将task执行数量提高,性能也会相应提高,这样的方式如果在运行中确实解决了数据倾斜是最好的,但是如果出现之前运行时候九OOM了,加大了reduce端task的数量,可以运行了,但是执行时间相当的长,那么就放弃这个方案
方案四:利用双重聚合
- 用于groupByKey和reduceByKey,比较适用于join,但是通常不用这样做,也就是说首先第一轮对key进行打散,将原来一样的key变成不一样的key(前面加前缀),相当于将一样的key分了多个组,然后进行局部聚合,接着除掉每个key的前缀,然后再进行全局聚合,进行两次聚合,避免数据倾斜问题
方案五:reduce join 转换成 map join
- 如果两个rdd进行join,有一个表比较小的化,可以将小表广播出去,这样每个节点的blockmanager中都有一份,这样的话根本不会发生shuffle,那么也就确定不会存在数据倾斜问题.如果join中有数据倾斜的情况,第一时间考虑这样的方式,但是如果两个表都很大,那么就不用这种方案,这种方案是牺牲一点点内存换来性能提升
方案六:sample抽样分解聚合
- 也就是说将倾斜的key单拉出来,然后用一个rdd进行打乱join
方案七:使用随机数和扩容进行join
- 也就是说通过faltMap进行扩容,然后再将随机数打入进去,再进行join,这样的话不能根本的解决数据倾斜,但是可以有效的缓解数据倾斜问题,也会提高性能
廿二.spark通信机制
spark消息通信主要分成三个部分:整体框架,启动消息通信,运行时消息通信
1.概述
- spark(旧版本)的远程进程通信(RPC)是通过Akka类库来实现的,Akka使用scala语言开发,基于Actor并发模型实现,Akka具有高可靠,高性能,可扩展等特点
2.具体通信流程
- 1)首先启动Master进程,然后启动所有的worker进程
- 2)worker启动后,在preStart方法中与Master建立连接,向Master发送注册信息,将worker的信息通过case class 封装起来发送给Master
- 3)Master接收到Worker的注册消息后将其通过集合保存起来,然后向worker反馈注册成功的消息
- 4)worker会定期向Master发送心跳包,领受新的计算任务
- 5)Master会定期清理超时的Worker
3.通信框架
- spark2.2使用Netty作为master与worker的通信框架,spark2.0之前使用的akka框架
- 1)spark启动消息通信:
- worker向master发送注册消息,master处理完毕后返回注册成功或者是失败的消息,如果成功,worker向master定时发送心跳
- 2)spark运行时消息通信
- 应用程序SparkContext向master发送注册消息,并由master为该应用分配Executor,executor启动之后会向SparkContext发送注册成功消息,然后SparkContext的rdd触发Action之后会形成一个DAG,通过DAGScheduler进行划分Stage并将其转化成TaskSet,然后TaskScheduler向Executor发送执行消息,Executor接收到信息之后启动并且运行,最后是由Driver处理结果并回收资源