spark内核揭秘-spark任务调度系统个人理解

前置知识

专业术语

1. 与任务相关

  1. Application:用户写的应用程序
  2. job:一个Action类算子触发执行的操作,action算子数=job数
  3. stage:一组任务(Task)
  4. task:在集群运行时最小的执行单元(Thread)

2. 与资源、集群相关

  1. Master:资源管理的主节点
  2. Worker:资源管理的从节点
  3. Executor:执行任务的进程
  4. ThreadPool:线程池,存在于Executor进程中

联系(待改正)

  • 一个Application中可能有多个job(这取决于代码中的action类算子数量),一个job中有多个stage(这取决于RDD的宽窄依赖关系),一个stage中可能有多个task
  • task运行在ThreadPool线程池中
  • Application中包括三部分(Driver Program + Executor)

RDD的依赖关系

1. 窄依赖

概念:父RDD与子RDD,partition之间的关系是一对一,那么父子RDD的依赖关系称之为窄依赖

什么是shuffle?
shuffle意思为洗牌(数据归整),是把Map阶段的数据抽离出来,按照一定的规则发送给对应的Reduce端

窄依赖没有shuffle</font在这里插入图片描述>

2. 宽依赖

概念:父RDD与子RDD,partition之间的关系是一对多,那么父子RDD的依赖关系称之为宽依赖,一般来说它会导致shuffle
在这里插入图片描述

groupByKey()函数是根据key值分区,默认情况下groupByKey返回的RDD分区数是与父RDD是一致的,但是当你传入参数时groupByKey(int n),此时的分区数是n。

3. 宽窄依赖的作用

目的,为什么要有依赖关系:为了将一个个的job切割成一个个的stage。

这是一个DAG有向无环图:用来描述切割job划分为stage的过程
在这里插入图片描述
上述图片中,stage1里只有RDDA,stage2中有CDEF,stage3中有BG。我们可以看出stage之间是宽依赖,stage内部里是窄依赖。

那么切割job为stage的规则是什么呢?
根据宽窄依赖切割,stage之间是宽依赖,stage内部里是窄依赖


形成一个DAG有向无环图需要从final RDD从后往前回溯,为什么?

因为父RDD不知道子RDD,子RDD知道父RDD。


为什么将一个个Job切成一个个stage?

stage之间是宽依赖有shuffle,stage内部里是窄依赖无shuffle
我们需要在每一个stage内部中分出一个个task。
最终目的:让每一个task以pipeline方式计算
task中存放的是它自己所贯穿的所有的partition中的计算逻辑,并且以递归函数的形式整合在一起

task为什么是pipeline的计算模式
  1. 因为spark没有读取文件的方式,他只能借助于MR的读文件的方法
  2. MR读文件的方式是一条一条的读的
  3. 当读取到的一条数据后它立马进行fun1的计算,但是此时也正在进行读取下一条数据的操作

这样的好处:没有数据落地(没有磁盘IO),没有中间结果,并行计算


看上述三个stage中每一个task(管道计算模式),数据是在什么时候落地?
  1. 如果stage后面是action类算子
    • collect:将每一个管道的计算结果收集到Driver端的内存中
    • saveAdTextFile:将每一个管道的计算结构写到指定目录(hdfs或者本地)
    • count:将管道的计算结果统计记录数,返回给Driver
  2. 如果stage后面是另外一个stage
    • 因为stage之间是宽依赖,一定有shuffle,在shuffle write阶段一定会有写磁盘

因为他要保证数据的安全写在磁盘里,若不写在磁盘里,则只能在内存中交换数据,因为内存不稳定,极其容易发生数据的丢失


在spark计算过程中,是不是非常的消耗内存?

不是,task的计算模式是管道模式,所有一个task任务中,最高只有一个管道的数据,所以它所占的内存不高;
除非
1.当时的并发task数非常高
2.使用了控制类算子,尤其是cache()


如果管道中有cache()逻辑,他是如何缓存数据的?

他会在管道的某个节点(计算逻辑有cache()的rdd)引出一个分支,使数据流向内存,所以当第一个task完全执行成功后,内存中才会有完整的数据。

RDD弹性分布式数据集,为什么不存数据还叫数据集?

虽然它不能存储数据,但是他能对数据进行操作。

RDD存储的内容

RDD中实际上存储的是计算逻辑,而不是真实的数据

为什么RDD不存储数据?
因为RDD是在内存中的,实际情况下spark一定会处理大量数据,所以大量数据不会放在内存里,只有放的是计算的逻辑。

下面举一个例子来说明:
下面是一个伪代码:

	Val RDD=sc.textFile(“hdfs://”)
	Val FilterRDD=rdd.filter(x=>(println(X);true)
	Val mapRDD=filterRDD.map(x=>(println(X);x)
	mapRDD.Count()

此图是伪代码的解释图:
在这里插入图片描述
task0:这条线所贯穿的所有的partition中的计算逻辑,并且以递归函数的形式整合在一起,例如:fun2(fun1(textFile(blcok1))),这个计算最好发送到block1或者它的副本节点上去计算

任务调度

Spark的核心是根据RDD来实现的,Spark Scheduler则为Spark核心实现的重要一环,其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据,根据RDD的依赖关系构建DAG,基于DAG划分Stage,将每个Stage中的任务发到指定节点运行。基于Spark的任务调度原理,我们可以合理规划资源利用,做到尽可能用最少的资源高效地完成任务计算。

什么是任务调度?

Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据

为什么要进行任务调度?

Spark是一个分布式的并行计算框架,Application会分布式的计算,大数据的计算原则是计算找数据,所以我们需要将Application任务调度到有数据的节点上。

任务调度的过程

在这里插入图片描述

1. RDD Objects

这一步将用户写的Application程序代码形成多张张(DAG)有向无环图,并且交给DAGScheduler。
可以说一个DAG对应一个job

2. DAGScheduler(主要作用)

  1. 它会根据RDD的宽窄依赖关系,将DAG有向无环图切割成一个个stage。
  2. 将切割出来的stage封装到TaskSet中,其实TaskSet=stage,然后将TaskSet交给TaskScheduler
  3. 当TaskScheduler重试某个task到达三次以上,它会向DAGScheduler汇报该task所在的stage失败,此时DAGScheduler会重试提交stage,注意:每一次重试提交的stage(TaskSet)已经成功执行的task不会再次分发到Execute进程中,只会重试未成功的task
  4. 若是DAGScheduler将此stage重试发送了4次之后还是失败,那么这个stage所在的job就失败了,job失败后不会重试
2.1 重试次数的配置信息

在这里插入图片描述

2.2需改配置信息的使用
  1. 代码中配置对象SparkConf对象的.set(key,value)方法
  2. (建议使用)在提交命令时,通过–conf来设置 spark-submit --master --conf k==v;若要修改多个配置信息的值,则需要加入多个–conf
  3. 在spark配置文件中配置,spark-default.conf中

3. TaskScheduler(主要作用)

TaskScheduler将获得的TaskSet遍历,获取到所有的Task,将Task部署到Worker节点的Execute进程的Thread Pool线程池中去执行

  1. TaskScheduler拿到TaskSet之后,会便利这个集合,得到每一个task。
  2. 对每一个task,然后调用HDFSD的某一个方法,获取数据的位置,然乎依据数据的位置,然后把task分发到此节点上(必须是worker)的Executor进程中的线程池中去执行。
  3. TaskScheduler会实时跟踪每一个Task的执行情况,若执行失败,TaskScheduler会重试提交task,不会无休止的重试,默认重试3次,如果这3次都是失败的,那么这个task所在的Stage就失败了,此时向DAGScheduler汇报。
  4. 若此task掉队(扎挣的任务),推测执行机制 此时TaskScheduler会重新提交一个和挣扎的task一模一样的task到其他的节点,但是挣扎的task不会被kill,他们同时运行,当其中一个执行成功后,kill掉另外一个task

所以说,存储集群HDFS最好包含了计算集群,使得计算任务可以直接发送到对应有处理数据的节点上执行。


挣扎的任务:
stage中有10000个task,9999个task执行完毕,1个正在运行,则这一个task则称为掉队或者挣扎的任务。

3.1 重试次数的配置信息

在这里插入图片描述

3.2 推测执行机制

推测执行机制就是上述的过程,但是推测执行机制是怎么推测的呢?
三个标准:

  1. 100ms
  2. 1.5倍
  3. 75%
    他们的描述:当所有的Task的75%以上全部执行完时,TaskScheduler会每隔100ms会计算一下集群中是否有挣扎的task。举个例子来说,100个task,76个执行完毕,24个正在执行,TaskScheduler会每隔100ms会计算一下挣扎的task,它会计算这24个task到此为止的已经执行的时间的中位数,然后乘以1.5得出一个时间,若这24个task中有的执行时间比这个计算出来的时间大,则这些task则被认定是挣扎的task。
3.2.1 如有有1T的数据,单机运行需要30min,但是使用Spark来计算则需要2h,为什么?
  1. 计算发生了数据倾斜(大部分的数据给了少量的task来计算,少量的数据给了大量task来计算)
  2. 开启了推测执行机制(默认是关闭的)
    举一个列子:一共有100个task,99个task处理1G,1task执行99G
    当99个task处理完1G后,它肯定比1个task处理的快,此时已经符合TaskScheduler推测执行机制了,它会把这一个task认为是挣扎的task,他会在别的节点上重新启动一个新的task,会启动n多个task,还是计算不完。
    关闭推测执行,配置信息:spark.speculation false
3.2.2 对于ETL类型的业务,开启推测执行、重试机制,对于最终的结果会不会有影响?

ETL:Extract抽取,Transform转换,Load转换;即数据清洗的过程
对于ETL的业务,所以每个task都会封装ETL的业务,当每个Task执行失败的时候(此时写入了一半的数据),此时肯定要重试,有写入了一个新的数据,和上述的数据相同,不过更全。推测执行也是相同的道理。
所有是有影响的(会有很多重复的数据)。
解决方案:

  1. 关闭所有各种推测,重试
  2. 设置一张事务表,入库效率虽然低,但是不会出现重复数据的错误了。(幂等思想,一个task执行了10好几次,但是只有一条入库)

4.Worker

执行Executor进程,在里面的ThreadPool池中执行task。

须知

任务调度的过程起到主要作用的是DAGScheduler和 TaskScheduler,它们是运行在Driver进程里的两个对象,Driver的大部分作用都是这两个对象的实现的

扩展

进程和线程的关系:
举例子:
进度相当于一个房子,线程相当于一个人。
人在房子中干活,但是房子本身不会干活。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值