文章目录
- 前置知识
- 专业术语
- 1. 与任务相关
- 2. 与资源、集群相关
- 联系(待改正)
- RDD的依赖关系
- 1. 窄依赖
- 2. 宽依赖
- 3. 宽窄依赖的作用
- 形成一个DAG有向无环图需要从final RDD从后往前回溯,为什么?
- 为什么将一个个Job切成一个个stage?
- task为什么是pipeline的计算模式
- 看上述三个stage中每一个task(管道计算模式),数据是在什么时候落地?
- 在spark计算过程中,是不是非常的消耗内存?
- 如果管道中有cache()逻辑,他是如何缓存数据的?
- RDD弹性分布式数据集,为什么不存数据还叫数据集?
- RDD存储的内容
- 任务调度
- 什么是任务调度?
- 为什么要进行任务调度?
- 任务调度的过程
- 1. RDD Objects
- 2. DAGScheduler(主要作用)
- 2.1 重试次数的配置信息
- 2.2需改配置信息的使用
- 3. TaskScheduler(主要作用)
- 3.1 重试次数的配置信息
- 3.2 推测执行机制
- 3.2.1 如有有1T的数据,单机运行需要30min,但是使用Spark来计算则需要2h,为什么?
- 3.2.2 对于ETL类型的业务,开启推测执行、重试机制,对于最终的结果会不会有影响?
- 4.Worker
- 须知
- 扩展
前置知识
专业术语
1. 与任务相关
- Application:用户写的应用程序
- job:一个Action类算子触发执行的操作,action算子数=job数
- stage:一组任务(Task)
- task:在集群运行时最小的执行单元(Thread)
2. 与资源、集群相关
- Master:资源管理的主节点
- Worker:资源管理的从节点
- Executor:执行任务的进程
- 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的计算模式
- 因为spark没有读取文件的方式,他只能借助于MR的读文件的方法
- MR读文件的方式是一条一条的读的
- 当读取到的一条数据后它立马进行fun1的计算,但是此时也正在进行读取下一条数据的操作
这样的好处:没有数据落地(没有磁盘IO),没有中间结果,并行计算
看上述三个stage中每一个task(管道计算模式),数据是在什么时候落地?
- 如果stage后面是action类算子
- collect:将每一个管道的计算结果收集到Driver端的内存中
- saveAdTextFile:将每一个管道的计算结构写到指定目录(hdfs或者本地)
- count:将管道的计算结果统计记录数,返回给Driver
- 如果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(主要作用)
- 它会根据RDD的宽窄依赖关系,将DAG有向无环图切割成一个个stage。
- 将切割出来的stage封装到TaskSet中,其实TaskSet=stage,然后将TaskSet交给TaskScheduler
- 当TaskScheduler重试某个task到达三次以上,它会向DAGScheduler汇报该task所在的stage失败,此时DAGScheduler会重试提交stage,注意:每一次重试提交的stage(TaskSet)已经成功执行的task不会再次分发到Execute进程中,只会重试未成功的task;
- 若是DAGScheduler将此stage重试发送了4次之后还是失败,那么这个stage所在的job就失败了,job失败后不会重试。
2.1 重试次数的配置信息
2.2需改配置信息的使用
- 代码中配置对象SparkConf对象的.set(key,value)方法
- (建议使用)在提交命令时,通过–conf来设置 spark-submit --master --conf k==v;若要修改多个配置信息的值,则需要加入多个–conf
- 在spark配置文件中配置,spark-default.conf中
3. TaskScheduler(主要作用)
TaskScheduler将获得的TaskSet遍历,获取到所有的Task,将Task部署到Worker节点的Execute进程的Thread Pool线程池中去执行
- TaskScheduler拿到TaskSet之后,会便利这个集合,得到每一个task。
- 对每一个task,然后调用HDFSD的某一个方法,获取数据的位置,然乎依据数据的位置,然后把task分发到此节点上(必须是worker)的Executor进程中的线程池中去执行。
- TaskScheduler会实时跟踪每一个Task的执行情况,若执行失败,TaskScheduler会重试提交task,不会无休止的重试,默认重试3次,如果这3次都是失败的,那么这个task所在的Stage就失败了,此时向DAGScheduler汇报。
- 若此task掉队(扎挣的任务),推测执行机制 此时TaskScheduler会重新提交一个和挣扎的task一模一样的task到其他的节点,但是挣扎的task不会被kill,他们同时运行,当其中一个执行成功后,kill掉另外一个task
所以说,存储集群HDFS最好包含了计算集群,使得计算任务可以直接发送到对应有处理数据的节点上执行。
挣扎的任务:
stage中有10000个task,9999个task执行完毕,1个正在运行,则这一个task则称为掉队或者挣扎的任务。
3.1 重试次数的配置信息
3.2 推测执行机制
推测执行机制就是上述的过程,但是推测执行机制是怎么推测的呢?
三个标准:
- 100ms
- 1.5倍
- 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,为什么?
- 计算发生了数据倾斜(大部分的数据给了少量的task来计算,少量的数据给了大量task来计算)
- 开启了推测执行机制(默认是关闭的)
举一个列子:一共有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执行失败的时候(此时写入了一半的数据),此时肯定要重试,有写入了一个新的数据,和上述的数据相同,不过更全。推测执行也是相同的道理。
所有是有影响的(会有很多重复的数据)。
解决方案:
- 关闭所有各种推测,重试
- 设置一张事务表,入库效率虽然低,但是不会出现重复数据的错误了。(幂等思想,一个task执行了10好几次,但是只有一条入库)
4.Worker
执行Executor进程,在里面的ThreadPool池中执行task。
须知
任务调度的过程起到主要作用的是DAGScheduler和 TaskScheduler,它们是运行在Driver进程里的两个对象,Driver的大部分作用都是这两个对象的实现的
扩展
进程和线程的关系:
举例子:
进度相当于一个房子,线程相当于一个人。
人在房子中干活,但是房子本身不会干活。