Spark 内核调度

面试高发

Spark DAG(重点理解)

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

在这里插入图片描述
上图是带有分区关系的DAG图。(只有代码执行起来才能画出来,因为这样才知道分区数,对应的数据在哪个分区上。上图还没有指定数据在哪个分区)

DAG标准定义:有向无环图
作用:标识代码的逻辑执行流程

示例:画如下代码的DAG图

# coding:utf8
import operator

from pyspark import SparkConf, SparkContext
from pyspark.storagelevel import StorageLevel
from defs import context_jieba, filter_words, append_words, extract_user_and_word
from operator import add

if __name__ == '__main__':
    # 0. 通过SparkConf对象构建SparkContext
    conf = SparkConf().setAppName("test").setMaster("local[*]")
    sc = SparkContext(conf=conf)

    # 1. 读取数据文件
    file_rdd = sc.textFile("../../data/SogouQ.txt")  # '23:00:02\t8181820631750396\titheima\t5\t2\thttp://www.itcast.cn'

    # 2. 对数据进行切分
    split_rdd = file_rdd.map(
        lambda x: x.split("\t"))  # ['08:00:22', '44801909258572364', 'hadoop', '1', '2', 'http://www.itcast.cn']

    # 3. 因为要做多个需求,split_rdd 作为基础rdd 会被多次使用,所以进行持久化操作
    split_rdd.persist(StorageLevel.DISK_ONLY)

    # TODO:需求1:用户搜索的关键词分析(wordCount)
    # 取出搜索内容
    context_rdd = split_rdd.map(lambda x: x[2])
    # 对搜索内容进行分词
    words_rdd = context_rdd.flatMap(context_jieba)
    # print(words_rdd)
    # 分词结果和实际有出入,所以还需要手动处理结果
    # 院校 帮 -> 院校帮
    # 博学 谷 -> 博学谷
    # 传智播 客 -> 传智播客
    # 将单个词 帮,谷,客过滤,最后直接替换为词条
    filtered_rdd = words_rdd.filter(filter_words)
    final_words_rdd = filtered_rdd.map(append_words)
    # 对单词进行排序,求出前五
    result1 = final_words_rdd.reduceByKey(lambda a, b: a + b).sortBy(lambda x: x[1], ascending=False,
                                                                     numPartitions=1).take(5)

    print("需求1结果:", result1)

    # TODO 需求2:用户和关键词组合分析
    # 用户id,我喜欢hadoop
    # 用户id+喜欢  用户id+hadoop   这样可以知道用户搜索频率
    user_content_rdd = split_rdd.map(lambda x: (x[1], x[2]))
    # 对用户搜索内容进行分词
    user_word_with_one_rdd = user_content_rdd.flatMap(extract_user_and_word)
    # 对内容进行分组聚合,排序,求前五
    result2 = user_word_with_one_rdd.reduceByKey(lambda a, b: a + b). \
        sortBy(lambda x: x[1], ascending=False, numPartitions=1). \
        take(5)
    print("需求2结果:", result2)

    # TODO 需求3:热门搜索时间段分析
    # 取出所有的时间
    time_rdd = split_rdd.map(lambda x: x[0])
    # 对时间进行处理,只保留小时精度即可
    hour_with_one_rdd = time_rdd.map(lambda x: (x.split(":")[0], 1))
    # lambda a, b: a + b 这个一直写很烦,python的operator包中提供了 加减乘除都有
    result3 = hour_with_one_rdd.reduceByKey(operator.add).\
        sortBy(lambda x:x[1],ascending=False,numPartitions=1).\
        collect()
    print("需求3结果:", result3)

对应DAG图
在这里插入图片描述
上图是没带分区关系的DAG图
也可以通过Spark的4040端口查看DAG图

显示由3个job
在这里插入图片描述

job0的DAG:
在这里插入图片描述

所以: 1个Action = 1个DAG = 1个Job
一段代码运行起来,在Saprk中称之为 Application

DAG和分区

代码不运行的时候我们能得到基础DAG。
代码运行起来之后,Saprk会基于分区的设定,在Saprk内部规划出来带有分区关系的DAG。
分区上的链条称为管道
在这里插入图片描述


DAG的宽窄依赖和阶段划分

Spark 的RDD之间有血缘关系,也就是有依赖关系,这个依赖关系可以分为 宽依赖和窄依赖
窄依赖:父RDD的一个分区,将全部数据发送给子RDD的一个分区(从父RDD看是一对一)
宽依赖:父RDD的一个分区,将数据发给子RDD的多个分区(从父RDD看是一对多)
宽依赖还有一个别名:shuffle

宽窄依赖作用就是为了在Saprk中对DAG进行阶段划分。
阶段划分: 对于Saprk来说,按照宽依赖划分不同的DAG阶段
划分依据:从后向前,遇到宽依赖,就划分出一个阶段,称之为stage

图例:
在这里插入图片描述
如图,可以看到,在DAG中,基于宽依赖,将DAG划分成了2个Stage
在Stage内部,一定都是 窄依赖

根据上图,RDD有三个分区,在 Stage 1 阶段,有三个数据流,明显是每个数据流由一个线程负责执行效率最高。
不可能说, b1 - > p1 是由线程1, p1(testRDD) - > p1(splitRDD) 是由线程2 。这样线程之间的数据交互肯定会走内存交互。如果说,线程1和线程2不属于同一个进程,还需要走网络IO。
综上所述,肯定一个数据流由一个线程负责处理效率最高。
这一部分数据都在这个线程内存中,所以这又称为“内存计算管道” PipLine。

阶段1 有3个 独立的 “内存计算管道”,由3个线程并行工作

阶段1 和 阶段0 之间存在shuffle,如果 task1 和 task4在同一个Executor上,也可以理解为同一台机器上(如果你一个机器一个Executor)。那么 task1 和 task4 之间的数据交互就不用走网路IO,直接内存地址交互就行,则task1 和 task5,6之间进行网络IO。

极端思想,让shuffle的数据交互全部走内存。这样我们失去了分布式的意义,所有线程都在一台机器上,可性能呢?这样就失去了分布式的意义。就是我们的Local模式。所以我们在并行和内存之间,并行的优先级大于内存的优先级。

spark默认受到全局并行度的限制,除了个别算子有特殊分区情况,大部分算子都会遵循全局并行度的要求,来规划自己的分区数,如果全局并行度为3,那么大部分算子分区都是3。
前面我们说过,不要自己随意改大分区,会引起shuffle。这儿就可以很好的解释了。分区改大了,会出现宽依赖,增加了阶段,管道变窄。性能就下降了。

横向:一个task可以处理多个RDD,但是一个task只能处理RDD的一个分区。
纵向:一个RDD有几个分区就需要几个task

总结:
Spark是怎么做内存计算的?

1、Spark会产生DAG图
2、DAG会基于分区和宽窄依赖划分阶段
3、一个阶段的内部都是窄依赖,窄依赖内,如果形成前后1:1的分区对应关系,就可以产生许多内存迭代计算管道
4、这些内存迭代计算的管道,就是一个个具体执行的Task
5、一个Task是一个具体的线程,任务跑在一个线程内,就是走内存计算

DAG作用?
为了划分出阶段

Stage阶段划分作用?
为了构建内存计算管道

Spark为什么比MapReduce快?
Spark算子丰富,MapReduce算子匮乏(Map和Reduce),MapReduce这个编程模型很难在一套MR中处理复杂的任务。很多复杂任务是需要写多个MapReduce进行串联,多个MR串联通过磁盘交互数据,性能低。
Spark可以执行内存迭代计算,算子之间形成DAG,基于依赖划分阶段后,在阶段内形成内存迭代计算管道,但是MapReduce之间的交互依旧是通过硬盘来交互的。
所以,编程模型上Spark占优(算子够多)。Spark算子交互上,和计算上,可以尽量多的内存计算而非磁盘迭代。

Spark 并行度

Spark 并行度:在同一时间内,有多少task在同时运行

并行度:并性能力的设置。
比如设置并行度6,其实就是要6个task并行在跑
在有了6个task并行的前提下,rdd的分区就被规划成了6个分区。
先有并行度,才有分区。不是先有分区再有并行度。

如何设置并行度:
优先级从高到低:
1、代码中
2、客户端提交参数中
3、配置文件中
4、默认(1,但是不会全部以1来跑,多数时候基于读取文件的分片数量来作为默认并行度)

全局并行度配置的参数:
spark.default.parallelism

配置文件中:

conf/spark-defaults.conf 文件中
spark.default.parallelism 100

在客户端提交参数中:

bin/spark-submit --conf "spark.default.parallelism=100"

在代码中设置

conf = SparkConf()
conf.set("spark.default.parallelism","100")

如何规划并行度
结论:设置为CPU总核心的 2 ~10 倍

为什么要设置最少2倍
CPU的核心同一时间只能干一件事
所以,在100个核心的情况下,设置100个并行,就能让CPU 100%出力
在这种设置下,如果task的压力不均衡,某个task先执行完了,就导致某个CPU核心空闲
所以我们将task(并行)分配的数量变多,比如800个并行,同一时间只有100个运行,700个等待
但是可以确保,某个task运行完了,后续有task补上,不让cpu闲下来,最大程度利用集群资源。

Spark任务调度

Spark的任务,由Driver进行调度,这个工作包含:
1、逻辑DAG产生
2、分区DAG产生
3、Task划分
4、将Task分配给Executor并监控其工作

在这里插入图片描述
如上图,Saprk程序的调度流程
1、Driver被构建出来
2、构建SaprkContext(执行环境入口对象)
3、基于DAG Schedule(DAG调度器)构建逻辑Task分配
4、基于TaskSchedule(Task调度器)将逻辑Task分配到各个Executor上干活,并监控它们
5、Worker(Excutor),被TaskSchedule管理监控,听从它们的指令干活,并定期汇报进度
1,2,3,4都是Driver的工作,5是Worker的工作

DAG调度器工作职责:将逻辑的DAG图进行处理,最终得到逻辑上的Task划分

Task调度器工作职责:基于DAG Schedule 的产出,来规划这些逻辑task,应该在哪些物理的executor上运行,以及监控管理它们运行。

总结

Spark层级关系梳理:
1、一个Spark环境可以运行多个Application
2、一个代码运行起来,会成为一个Application
3、Application内部可以有多个job
4、每个Job由一个Action产生,并且每个Job都有自己的DAG图
5、一个Job的DAG图 会基于宽窄依赖划分为不同的阶段
6、不同阶段内 基于分区数量,形成多个并行的内存迭代管道
7、每一个内存迭代管道形成一个Task(DAG调度器划分将Job内划分出具体的task任务,一个Job被划分出来task 在逻辑上称之为这个job的taskset)

1、DAG有什么用?
DAG有向无环图,用以描述任务执行流程,主要作用是协助DAG调度器构建Task分配用以做任务管理

2、内存迭代\阶段划分?
基于DAG的宽窄依赖划分阶段,阶段内部都是窄依赖,可以构建内存迭代的管道

3、DAG调度器是?
构建Task,分配用以做任务管理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值