Spark原理
- Spark专业术语
-任务:
Application:用户写的应用程序(Driver Program+Executor Program)
Job:一个action类算子触发执行的操作
Stage:一组任务
task:(thread)在集群运行时最小的执行单元
-资源,集群:
Master:资源管理的主节点
Worker:资源管理的从节点
Executor:执行任务的进程
ThreadPool:线程池,存在于Executor进程中 - RDD(Resilient Distributed Dataset)弹性分布式数据集
1.RDD是由一系列的partition组成的
2.RDD提供的每一个函数实际上是作用在每一个partition上的
3.RDD是由一系列的依赖关系的,依赖于其他的RDD
4.可选项 分区器是作用于KV格式的RDD上的
5.可选项 RDD会提供一系列的最佳计算位置(RDD提供了一个接口方法,直接调用这个方法接口,就能拿到这个RDD的所有的Partition位置)
在Spark中没有读文件的方法,依赖的是MR读文件的方法,MR在读文件之前会将文件划分成一个个split,split size = block size,block num ≈ split num,split num等于第一个RDD的分区数。(特殊情况除外)
RDD依赖关系的作用:计算的容错性。如果在计算过程中出现数据丢失问题,RDD会从其父RDD重新计算过来。
RDD不存数据,它只是一个逻辑上的概念,partition也不存数据,它们有处理数据的能力。RDD中实际存储的是计算逻辑。 - RDD的依赖关系
-窄依赖:
父RDD不知道其有多少子RDD,但是子RDD一定知道其父RDD,父RDD与子RDD,Partition之间的关系是一对一,那么父子RDD的依赖关系就称之为窄依赖,这种依赖不会有shuffle。
-宽依赖:
父RDD与子RDD,Partition之间的关系是一对多,那么父子RDD的依赖关系就称之为宽依赖,这种依赖会有shuffle。
-宽依赖的作用:
为了将一个个Job切割成一个个Stage
为什么要将Job切割成Stage?
Stage与Stage之间是宽依赖没有shuffle
Stage内部是窄依赖没有shuffle
为了形成pipeline管道并行计算
task0:这条红线所贯穿的所有partition中的计算逻辑,并且以递归函数展开式的形式整合在一起(例:fun2(fun1(textFile(bl))))计算时最好发送到b1或b1副本所在的节点
task1:(例:fun2(fun1(textFile(b2))))计算时最好发送到b2或b2副本所在的节点
-task的计算模式是pipeline的计算模式,管道计算。
1.Stage中的每个task(管道计算模式)在什么时候会落地磁盘呢?
①.如果Stage后面是action类算子
collect:将每一个管道的计算结果收集到Driver端的内存中
saveAsTextFile:将每一个管道的计算结果写到指定目录
count:将管道的计算结果统计记录数,返回给Driver
②.如果Stage后面是Stage
在shuffle write阶段会写磁盘
shuffle write阶段写磁盘是为了防止reduce task拉取数据失败
2.Spark在计算过程中并不是非常耗内存,,有控制类算子时最耗内存(例:cache())
3.RDD有创造数据的能力所以叫数据集 - 任务调度
1.根据RDD的宽窄依赖关系,将DAG有向无环图切割成一个个的Stage,将切割出来的Stage封装到另外一个对象TaskSet,然后将一个个TaskSet给TaskScheduler。
2.TaskSchedule拿到TaskSet后,会遍历这个集合,拿到每一个task,然后去调用HDFS上某一个方法,然后获取数据的位置,依据数据的位置来分发task到Worker节点的Executor进程中的线程池中执行
3.TaskSchedule会实时跟踪每一个task的执行情况,若执行失败或者遇到挣扎(掉队)的任务,TaskSchedule会重试提交task,默认重复3次,如果重试3次依然失败,那么这个task所在的Stage就失败了,此时它会向DriverSchedule汇报
4.此时DriverSchedule会重试提交Stage,注意:每一次重试提交的Stage,已经成功执行的不会再次分发到Executor进程执行,只是重试失败的,如果DriverSchedule重试了4次依然失败,那么这个Stage所在的Job就失败了,Job失败是不会进行重试的。
-挣扎(掉队)的任务:
默认情况,在任务的75% 执行完毕后,TaskSchedule会每100ms计算一下,统计还未完成的任务已执行的时间取出中位数,然后将这个中位数*1.5,拿到这个最终计算出来的时间去看哪一些task超时,这些task就是挣扎(掉队)的task。当遇到挣扎(掉队)的任务,TaskSchedule会进行重试,此时TaskSchedule会提交一个和挣扎的task同样的task到集群中运行,挣扎的task并不会被kill掉,而是会比赛执行,谁先执行完毕就取谁的结果。
-配置信息的使用:
1.在代码中设置SparkConf
2.在提交Application时通过 --conf来设置,例:spark-submit --master --conf k=v 如果要修改多个配置信息的值,那么需要加一个 --conf(常用)
3.在spark的配置文件中配置,spark-default.conf - 资源调度
-资源调度详细过程:
1.客户端提交命令,在客户端启动一个sparksubmit进程
2.sparksubmit进程为Driver向Master申请资源,此时Master中将申请信息加入waitingDrivers集合,当waitingDrivers集合中元素不为空时,说明有客户向master为Driver申请资源,此时应当查看当前集群的资源情况,找到符合需求的节点,启动Driver,当Driver启动成功时,这个资源的申请信息会从waitingDrivers中删除
3.Driver启动成功后会向Master为当前的Application申请资源,在Master中将申请信息加入waitingApps,当waitingApps集合不为空时,说明有Driver向Master为Application申请资源,Master会查看集群的资源情况找到合适的Worker节点,启动executor进程,默认每一个Worker为当前Application只启动一个executor,这个executor会使用1G内存和这个Worker的所管理的所有core
-资源调度总结:
1.默认情况下,每一个节点为当前的Application只会启动一个executor,这个executor默认使用1G内存和当前Worker所能管理的所有的core
2.如果要想在一个Worker上启动多个executor,在提交Application时要指定executor使用的核心数提交时加上--executor-cores
3.默认情况下,executor的启动是轮训方式来启动的,轮训一定程度有利于数据本地化