31-spark的算子使用及调度流程

17.6 算子(多文件)⭐️

17.6.1 转换算子

  1. 转换组 join

    join后的分区数与父RDD分区数多的那一个相同,我们也可以手动设置新的RDD的分区数

    • leftOuterJoin
      • 按照Key等值关联,然后显示左面不满足条件的
    • rightOuterJoin
      • 按照Key等值关联,然后显示右面不满足条件的
    • fullOuterJoin
      • 按照Key等值关联,然后显示左右所有不满足条件的
    • Join
      • 按照Key等值关联
    • 这些 join 都是作用在 K,V 格式的 RDD 上。根据 key 值进行连接,例如: (K,V)join(K,W)返回(K,(V,W))
    • 代码实现
    object Hello07Join {
      def main(args: Array[String]): Unit = {
        //1.配置并创建对象
        val sparkContext = new SparkContext((new SparkConf().setMaster("local").setAppName("Join" + System.currentTimeMillis())))
        //读取数据
        //    val array = Array[String]("Hello1 user1", "Hello2 user2", "Hello user", "Hello user", "user1 apple")
        //    val lines: RDD[String] = sparkContext.parallelize(array, 8)
        //    println(lines.getNumPartitions)
        //读取数据
        //    val linesPart: RDD[String] = sparkContext.textFile("src/main/resources/part.txt", 4)
        //    println(linesPart.getNumPartitions)
    
        //开始关联数据 `(K,V)join(K,W)返回(K,(V,W))`
        val array1 = Array[String]("Hello1 user1", "Hello2 user1", "Hello user11", "Hello user12", "user1 apple1")
        val array2 = Array[String]("Hello1 user2", "Hello2 user2", "Hello user21", "Hello user22", "user2 apple2")
        val lines1 = sparkContext.parallelize(array1, 3)
        val lines2 = sparkContext.parallelize(array2, 4)
        val words1 = lines1.map(ele => (ele.split(" ")(0), ele.split(" ")(1)))
        val words2 = lines2.map(ele => (ele.split(" ")(0), ele.split(" ")(1)))
    
        //Join(按照Key等值关联)
        words1.join(words2).foreach(ele => println("join=>" + ele))
        //leftOuterJoin (按照Key等值关联,然后显示左面不满足条件的)
        words1.leftOuterJoin(words2).foreach(ele => println("leftOuterJoin=>" + ele))
        //rightOuterJoin (按照Key等值关联,然后显示右面不满足条件的)
        words1.rightOuterJoin(words2).foreach(ele => println("rightOuterJoin=>" + ele))
        //fullOuterJoin (按照Key等值关联,然后显示左右所有不满足条件的)
        words1.fullOuterJoin(words2).foreach(ele => println("fullOuterJoin=>" + ele))
    
        //查看分区数
        println("合并后的分区数join:" + words1.join(words2, 5).getNumPartitions)
        println("合并后的分区数leftOuterJoin:" + words1.leftOuterJoin(words2, 7).getNumPartitions)
    
      }
    }
    
  2. Union

    • 合并两个数据集。两个数据集的类型要一致。
    • 返回新的 RDD 的分区数是合并 RDD 分区数的总和
  3. intersection

    • 取两个数据集的交集
    • 返回新的 RDD 的分区数是RDD分区数最多的那个
  4. subtract

    • 取两个数据集的差集。
    • 返回新的 RDD 的分区数是subtract前面的那个RDD的分区数
  5. mapPartitions

    • mapPartition与 map 类似,单位是每个 partition 上的数据。
    • 分区数不会发生变化
  6. distinct(map+reduceByKey+map)

    • 对 RDD 内数据去重。
    • 对整个对象匹配,完全相同的(K,V)时候才会去重
  7. cogroup

    • 当调用类型 (K,V) 和 (K,W) 的数据上时,返回一个数据集 (K,(Iterable<V>,Iterable<W>))
    • 返回新的 RDD 的分区数是RDD最多的那个分区数,
    • 会对所有列都进行拼接,如果没有,迭代器为NIL

17.6.2 行动算子

  • foreachPartition
    • 遍历的数据是每个 partition 的数据

17.7 窄依赖和宽依赖

  1. 宽窄依赖提出的背景
    • 在Spark中,每个任务对应一个分区,通常不会跨分区操作数据。但如果遇到宽依赖的操作,Spark必须从所有分区读取数据,并查找所有键的对应值,然后汇总在一起以计算每个键的最终结果,这称为Shuffle。Shuffle是一项昂贵的操作,因为它通常会跨节点操作数据,这会涉及磁盘 I/O,网络 I/O,和数据序列化。某些Shuffle操作还会消耗大量的堆内存,因为它们使用堆内存来临时存储需要网络传输的数据
  2. 宽窄依赖的产生
    • RDD 之间有一系列的依赖关系,
    • 在spark的执行过程中,RDD经过transformation(转换)算子之后,最后由action算子触发操作。逻辑上每经历一次转换,就会将RDD转换为一个新的RDD,新的RDD和旧的RDD之间通过lineage(血统)产生依赖关系,这个关系在容错中有很重要的作用,而依赖也分为宽依赖和窄依赖
  3. 宽窄依赖的作用?
    • 宽窄依赖是为了数据传输更加高效
  4. 宽窄依赖的划分
    • 如果子RDD的一个分区完全依赖父RDD的一个或多个分区,则是窄依赖,否则就是宽依赖

17.7.1 窄依赖

  1. 概念理解
    • 父RDD和子RDD的partition之间的关系式一对一的
    • 父 RDD 和子 RDD 的 partition 关系是多对一的
  2. 特点
    • 由于下游数据只有一条,所以不需要对数据进行shuffle操作
    • 窄依赖可以组装在一起一次性将数据算出来,然后进行下一步操作,可以减少数据的拉取操作
      • 窄依赖由于不需要进行Shuffle,可以将父RDD和子RDD连接到一起,从而减少数据的传输

17.7.2 宽依赖

  1. 概念理解
    • 父 RDD 与子 RDD 的 partition 之间的关系是一对多
  2. 特点
    • 由于下游数据有多条,所以需要对数据进行shuffle操作

17.7.3 宽窄依赖图解

图解一:

image-20220721152735518

图解二:

图解三:

17.8 Stage⭐️

Spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分成相互依赖的多个Stage,划分Stage的依据就是RDD之间的宽窄依赖,遇到宽依赖就划分stage,每个stage包含一个或者多个task任务,然后将这些task以taskSet的形式提交给TaskScheduler运行

Spark任务–>DAG有向无环图–>DAGScheduler–>n个Stage–>n个task任务–>TaskScheduler

stage是由一组并行的task组成

17.8.1 Stage切割规则

  1. 切割规则

    • 从后往前推RDD算子,如果遇到宽依赖就断开,划分为一个stage;
    • 如果遇到窄依赖就将这个RDD加入当前的stage。
      • 因为前面的转换算子是懒加载,只有遇到后面的行动算子才会执行,所以只有从行动算子开始才能向前切割stage
  2. 具体的切割流程

    • 从后向前推理,遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到Stage中
    • 每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition数量决定的
    • 最后一个Stage里面的任务的类型是ResultTask,前面所有其他Stage里面的任务类型都是ShuffleMapTask
      • 对于窄依赖,由于分区依赖关系的确定性,partition的转换处理可以在同一个线程里完成,称之为ResultTask。
      • 而对于宽依赖,只能等父RDD集的shuffle处理完成后,在下一个stage才能开始接下来的计算,称之为shuffleMapTask
    • 代表当前Stage的算子一定是这个Stage的最后一个计算步骤
  3. 总结

    • 由于Spark中的stage的划分是根据shuffle来划分的,而宽依赖必然有shuffle过程,因此可以说spark是根据宽窄依赖来划分stage的
  4. 举例

    • 如下图的Spark任务应该被划分为2个stage

17.8.2 Stage计算模式

image-20220721161941589

  1. pipeline管道计算模式

    • pipeline只是一种计算思想、模式
  2. pipeline和partition关系

    • 在spark中pipeline是一个partition接着对应一个partition(partition关系是一对一),所以在stage内部只有窄依赖
    • 可以理解为pipeline是一个流水线(如上图的红色横线代表一个pipeline)
  3. 数据在管道里面的落地时机

    • 对RDD进行持久化( cache , persist )时数据会落地
    • shuffle write 的时候
  4. task与RDD的partition的关系

    • stage的task的并行度是由stage的最后一个RDD的分区数来决定的,有多少个task就有多少个pipeline
  5. 如何改变RDD的分区数

    • reduceByKey(XXX,3)
    • GroupByKey(4)
    • sc.textFile(path,numpartition)
    • 使用算子时传递 分区num参数 就是分区 partition 的数量
  6. 测试验证 pipeline 计算模式

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("pipeline");
    val sc = new SparkContext(conf)
    val rdd = sc.parallelize(Array(1,2,3,4))
    val rdd1 = rdd.map { x => {
        println("map--------"+x)
        x
    }}
    val rdd2 = rdd1.filter { x => {
        println("fliter********"+x)
        true
    } }
    rdd2.collect()
    sc.stop()
    

17.9 SparkShuffle

  1. 对Shuffle的理解

    • 在Spark中,每个任务对应一个分区,通常不会跨分区操作数据。但如果遇到宽依赖的操作,Spark必须从所有分区读取数据,并查找所有键的对应值,然后汇总在一起以计算每个键的最终结果,这称为Shuffle。Shuffle是一项昂贵的操作,因为它通常会跨节点操作数据,这会涉及磁盘 I/O,网络 I/O,和数据序列化。某些Shuffle操作还会消耗大量的堆内存,因为它们使用堆内存来临时存储需要网络传输的数据
    • 通俗的理解,就是2个Stage之间,数据进行传递的过程就叫Shuffle
  2. Shuffle的图解

17.9.1 SparkShuffle概念

  1. ReduceByKey的作用
    • reduceByKey会将上一个RDD中的每一个key对应的所有value聚合成一个value,然后生成一个新的RDD,元素类型是<key,value>对的形式,这样每一个key对应一个聚合起来的value
  2. 对于ReduceByKey出现的问题的提出
    • 聚合之前,每一个key对应的value不一定都是在一个partition中,也不太可能在同一个节点上,因为RDD是分布式的弹性数据集,RDD的partition很有可能分布在各个节点上
  3. 聚合的解决
    • Shuffle Write:上一个Stage的每个map task就必须保证将自己处理的当前分区的数据,相同的key写入一个分区文件中,可能会写入多个不同的分区文件中
    • Shuffle Read:reduce task就会从上一个stage的所有task所在的机器上寻找属于自己的那些分区文件,这样就可以保证每一个key所对应的value都会汇聚到同一个节点上去处理和聚合。
  4. Spark的Shuffle类型
    • HashShuffle
    • SortShuffle
      • Spark1.2之前是HashShuffle
      • Spark1.2引入SortShuffle
      • Spark2.0就只有sortshuffle

17.9.2 HashShuffle

17.9.3 SortShuffle

17.9.4 Shuffle文件寻址

17.9.5 Shuffle调优

17.10 Spark资源调度和任务调度⭐️

image-20220721165849614

17.10.1 调度流程

image-20220721175602921

image-20220721175724649

  1. 启动集群后,worker节点会向Master节点汇报资源情况,Master掌握了集群资源的情况
  2. 当Spark提交一个Application后,根据RDD之间的依赖关系将Application转换成一个DAG的有向无环图
  3. 任务提交后,Spark会在Driver端创建两个对象:DAGScheduler和TaskScheduler,(DAGScheduler 是任务调度的高层调度器,TaskScheduler 是任务调度的低层调度器)
  4. DAGScheduler 的主要作用是将DAG根据RDD之间的宽窄依赖关系划分为一个个的Stage,然后将这些Stage以TaskSet的形式提交给TaskScheduler
    • TaskScheduler 是任务调度的低层调度器,这里 TaskSet 其实就是一个集合,里面封装的就是一个个的 task 任务,也就是 stage 中的并行的 task 任务
  5. TaskScheduler会遍历TaskSet集合,拿到每个task后会将task发送到Executor中去执行(其实就是发送到 Executor 中的线程池 ThreadPool 去执行)
  6. task在Executor线程池中的运行情况会向TaskScheduler反馈,当task执行失败时,则由TaskScheduler负责重试,将task重新发送给Executor去执行,默认重试3次。如果重试3次依然失败,那么这个task所在的stage就发送失败了
  7. stage失败了,则由DAGScheduler来负责重试,重新发送TaskSet到TaskScheduler,Stage默认重试4次,如果重试4次以后依然失败,那么这个 job 就失败了。 job 失败了, Application 就失败了
  8. Spark 的推测执行机制
  • TaskScheduler 不仅能重试失败的 task ,还会重试 straggling (落后,缓慢) task ( 也就是执行速度比其他task慢太多的task )。如果有运行缓慢的 task 那么 TaskScheduler 会启动一个新的task 来与这个运行缓慢的 task 执行相同的处理逻辑。两个 task 哪个先执行完,就以哪个 task的执行结果为准。这就是 Spark 的推测执行机制。在 Spark 中推测执行默认是关闭的。推测执行可以通过spark.speculation 属性来配置

  • 注意:

    • 对于 ETL 类型要入数据库的业务要关闭推测执行机制,这样就不会有重复的数据入库。
    • 如果遇到数据倾斜的情况,开启推测执行则有可能导致一直会有 task 重新启动处理相同的逻辑,任务可能一直处于处理不完的状态
  • 对调度流程的总结

    1. 提交Application

      • 根据RDD之间的依赖关系将Application转换成一个DAG
      • Driver端创建两个对象来处理Application
    2. 处理Application

      • 正常发送的处理
        • DAGScheduler作用:
        • TaskScheduler作用:
      • 失败发送的处理
        • Stage发送失败
        • Job发送失败
    3. Spark 的推测执行机制

    4. 各个节点的作用

      • worker节点:向Master节点汇报当前资源情况
      • Master节点:收集集群的资源情况
    5. 各个节点的关系流程

17.10.2 流程图解

图解一:

image-20220721165821801

图解二:

image-20220721175534854

17.10.3 粗细粒度资源申请

1. 粗粒度资源申请(Spark)
  1. 概念理解
    • 在Application执行之前,将所有的资源申请完毕,当资源申请成功后,才会进行任务的调度,当所有的task执行完成后,才会释放这部分资源
    • 先申请完所有所需的资源,在执行任务,结束后释放所有资源
  2. 优点
    • 在 Application 执行之前,所有的资源都申请完毕,每一个 task 直接使用资源就可以了,不需要 task 在执行前自己去申请资源
    • task 启动快了, task 执行快了, stage 执行就快了,job 就快了, application 执行就快了
  3. 缺点
    • 直到最后一个 task 执行完成才会释放资源,集群的资源无法充分利用
  4. 举例
    • Spark
2. 细粒度资源申请(MR)
  1. 概念理解
    • Application执行前不需要先申请资源,而是直接执行,当需要资源时,让job中的每一个task在执行前自己去申请资源,task执行完成后就释放资源
  2. 优点
    • 集群的资源可以充分利用,而不必占用整个资源
  3. 缺点
    • task自己去申请资源,task启动变慢,从而导致Application的运行变慢了
  4. 举例
    • MapReduce
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值