Spark原理——逻辑执行图

逻辑执行图

  • 明确逻辑计划的边界

    在 Action 调用之前,会生成一系列的RDD,这些RDD之间的关系,其实就是整个逻辑计划

    val conf= new SparkConf().setMaster("local[6]").setAppName("wordCount_source")
    val sc= new SparkContext(conf)
    
    val textRDD=sc.parallelize(Seq("Hadoop Spark", "Hadoop Flume", "Spark Sqoop"))
    val splitRDD=textRDD.flatMap(_.split(" "))
    val tupleRDD=splitRDD.map((_, 1))
    val reduceRDD=tupleRDD.reduceByKey(_ + _)
    val strRDD=reduceRDD.map(item => s"${item._1},${item._2}")
    
    println(strRDD.toDebugString)
    strRDD.collect.foreach(item =>println(item))
    

    例如上述代码,如果生成逻辑计划的,会生成效如下一些RDD,这些RDD是相互关联的,这些RDD之间,其实本质上生成的就是一个 计算链

    在这里插入图片描述

    接下来, 采用迭代渐进式的方式, 一步一步的查看一下整体上的生成过程

  • RDD 如何生成

    • **textFile算子的背后**

      研究 RDD 的功能或者表现的时候, 其实本质上研究的就是 RDD中的五大属性, 因为 RDD透过五大属性来提供功能和表现, 所以如果要研究 textFile 这个算子, 应该从五大属性着手, 那么第一步就要看看生成的 RDD是什么类型的 RDD

      1. **textFile 生成的是 HadoopRDD**

        在这里插入图片描述

        在这里插入图片描述

        在这里插入图片描述

      2. **HadoopRDDPartitions对应了 HDFSBlocks**

        在这里插入图片描述

        其实本质上每个 HadoopRDDPartition都是对应了一个 HadoopBlock, 通过 InputFormat 来确定 Hadoop中的 Block 的位置和边界, 从而可以供一些算子使用

        在这里插入图片描述

      3. **HadoopRDDcompute函数就是在读取 HDFS中的 Block**

        本质上, compute 还是依然使用 InputFormat来读取 HDFS中对应分区的 Block

      4. **textFile这个算子生成的其实是一个 MapPartitionsRDD**

        textFile这个算子的作用是读取 HDFS上的文件, 但是 HadoopRDD中存放是一个元组, 其 Key是行号, 其 ValueHadoop中定义的 Text对象, 这一点和 MapReduce程序中的行为是一致的

        但是并不适合 Spark 的场景, 所以最终会通过一个 map算子, 将 (LineNum, Text)转为 String形式的一行一行的数据, 所以最终 textFile这个算子生成的 RDD 并不是 HadoopRDD, 而是一个 MapPartitionsRDD

    • **map算子的背后**

      在这里插入图片描述

      • **map算子生成了 MapPartitionsRDD**

         val conf= new SparkConf().setMaster("local[6]").setAppName("wordCount_source")
        val sc= new SparkContext(conf)
        
        val rdd=sc.parallelize(Seq("Hadoop Spark", "Hadoop Flume", "Spark Sqoop"))
        val rdd1=rdd.flatMap(_.split(" "))
        val rdd2=rdd1.map((_, 1))
        

        由源码可知, 当 val rdd2 = rdd1.map() 的时候, 其实生成的新 RDD 是 rdd2, rdd2 的类型是 MapPartitionsRDD, 每个 RDD 中的五大属性都会有一些不同, 由 map 算子生成的 RDD 中的计算函数, 本质上就是遍历对应分区的数据, 将每一个数据转成另外的形式

      • **MapPartitionsRDD的计算函数是 collection.map( function )**

        真正运行的集群中的处理单元是 Task, 每个 Task 对应一个 RDD 的分区, 所以 collection 对应一个 RDD 分区的所有数据, 而这个计算的含义就是将一个 RDD 的分区上所有数据当作一个集合, 通过这个 Scala 集合的 map 算子, 来执行一个转换操作, 其转换操作的函数就是传入 map 算子的 function

      • 传入 map算子的函数会被清理

        在这里插入图片描述

        这个清理主要是处理闭包中的依赖, 使得这个闭包可以被序列化发往不同的集群节点运行

    • **flatMap算子的背后**

      在这里插入图片描述

      flatMapmap算子其实本质上是一样的, 其步骤和生成的 RDD都是一样, 只是对于传入函数的处理不同, mapcollect.map( function )flatMapcollect.flatMap( function )

      从侧面印证了, 其实 Spark中的 flatMapScala基础中的 flatMap其实是一样的

      val conf= new SparkConf().setMaster("local[6]").setAppName("wordCount_source")
      val sc= new SparkContext(conf)
      
      val textRDD=sc.parallelize(Seq("Hadoop Spark", "Hadoop Flume", "Spark Sqoop"))
      val splitRDD=textRDD.flatMap(_.split(" "))
      val tupleRDD=splitRDD.map((_, 1))
      val reduceRDD=tupleRDD.reduceByKey(_ + _)
      val strRDD=reduceRDD.map(item => s"${item._1},${item._2}")
      
      // println(strRDD.toDebugString)
      strRDD.collect.foreach(item =>println(item))
      

      textRDDsplitRDDtupleRDD

      textRDDsplitRDD再到 tupleRDD的过程, 其实就是调用 mapflatMap算子生成新的 RDD的过程, 所以如下图所示, 就是这个阶段所生成的逻辑计划

  • RDD 之间有哪些依赖关系

    • 前置说明

      • 什么是RDD之间的依赖关系?

        在这里插入图片描述

        • 什么是关系(依赖关系)?

          从算子视角上来看,splitRDD 通过 map 算子得到了 tupleRDD ,所以 splitRDD 和 tupleRDD 之间的关系是 map, 但是仅仅这样说,会不够全面,从细节上来看,RDD只是数据和关于数据的计算,而具体执这种计算得出结果的是一个神秘的其它组件,所以,这两个 RDD 的关系可以表示为 splitRDD 的数据通过 map 操作,被传入 tupleRDD ,这是它们之间更细化的关系

          但是 RDD 这个概念本身并不是数据容器,数据真正应该存放的地方是 RDD 的分区,所以如果把视角放在数据这一层面上的话,直接讲这两个 RDD 之间有关系是不科学的,应该从这两个 RDD 的分区之间的关系来讨论它们之间的关系

        • 那这些分区之间是什么关系?

          如果仅仅说 splitRDD 和 tupleRDD 之间的话,那它们的分区之间就是一对一的关系

          但是 tupleRDD 到 reduceRDD 呢?tupleRDD 通过算子 reduceByKey 生成 reduceRDD ,而这个算子是一个 Shuffle 操作,Shuff1e 操作的两个 RDD 的分区之间并不是一对一,reduceByKey 的一个分区对应 tupleRDD 的多个分区

      • reduceByKey 算子会生成 ShuffledRDD

        reduceByKey 是由算子 combineByKey 来实现的,combineByKey 内部会创建 ShuffledRDD 返回,而整个reduceByKey操作大致如下过程

        在这里插入图片描述

        去掉两个 reducer 端分区,只留下一个的话,如下

        在这里插入图片描述

        所以, 对于 reduceByKey 这个 Shuffle 操作来说, reducer 端的一个分区, 会从多个 mapper 端的分区拿取数据, 是一个多对一的关系

        至此为止, 出现了两种分区见的关系了, 一种是一对一, 一种是多对一

    • 窄依赖

      窄依赖(NarrowDependency)

      假如 rddB = rddA.transform(…), 如果 rddB 中一个分区依赖 rddA 也就是其父 RDD 的少量分区, 这种 RDD 之间的依赖关系称之为窄依赖

      换句话说, 子 RDD 的每个分区依赖父 RDD 的少量个数的分区, 这种依赖关系称之为窄依赖

      @Test
      def narrowDependency(): Unit = {
        // 需求:求得两个 RDD 之间的笛卡尔积
      
        // 1. 生成 RDD
        val conf = new SparkConf().setMaster("local[6]").setAppName("cartesian")
        val sc = new SparkContext(conf)
      
        val rddA = sc.parallelize(Seq(1, 2, 3))
        val rddB = sc.parallelize(Seq("a", "b"))
      
        // 2. 计算
        val rddC = rdd1.cartesian(rdd2)
      
        // 3. 结果获取
        rddC.collect().foreach(print(_))
      
        sc.stop()
      }
      // 运行结果: (1,a) (1,b) (2,a) (2,b) (3,a) (3,b)
      
      • 上述代码的 cartesian 是求得两个集合的笛卡尔积
      • 上述代码的运行结果是 rddA 中每个元素和 rddB 中的所有元素结合, 最终的结果数量是两个 RDD 数量之和
      • rddC 有两个父 RDD, 分别为 rddA 和 rddB

      对于 cartesian 来说, 依赖关系如下

      在这里插入图片描述

      上述图形中清晰展示如下现象

      • rddC 中的分区数量是两个父 RDD 的分区数量之乘积
      • rddA 中每个分区对应 rddC 中的两个分区 (因为 rddB 中有两个分区), rddB 中的每个分区对应 rddC 中的三个分区 (因为 rddA 有三个分区)

      它们之间是窄依赖, 事实上在 cartesian 中也是 NarrowDependency 这个所有窄依赖的父类的唯一一次直接使用, 为什么呢?

      因为所有的分区之间是拷贝关系, 并不是 Shuffle 关系

      • rddC 中的每个分区并不是依赖多个父 RDD 中的多个分区
      • rddC 中每个分区的数量来自一个父 RDD 分区中的所有数据, 是一个 FullDependence, 所以数据可以直接从父 RDD 流动到子 RDD
      • 不存在一个父 RDD 中一部分数据分发过去, 另一部分分发给其它的 RDD
    • 宽依赖

      宽依赖(ShuffleDependency

      并没有所谓的宽依赖, 宽依赖应该称作为 ShuffleDependency

      在 ShuffleDependency 的类声明上如下写到

      Represents a dependency on the output of a shuffle stage.
      

      上面非常清楚的说道, 宽依赖就是 Shuffle 中的依赖关系, 换句话说, 只有 Shuffle 产生的地方才是宽依赖

      那么宽窄依赖的判断依据就非常简单明确了, 是否有 Shuffle ?

      举个 reduceByKey 的例子, rddB = rddA.reduceByKey( (curr, agg) ⇒ curr + agg ) 会产生如下的依赖关系

      在这里插入图片描述

      在这里插入图片描述

      • rddB 的每个分区都几乎依赖 rddA 的所有分区
      • 对于 rddA 中的一个分区来说, 其将一部分分发给 rddB 的 p1, 另外一部分分发给 rddB 的 p2, 这不是数据流动, 而是分发
    • 如何分辨宽窄依赖 ?

      其实分辨宽窄依赖的本身就是在分辨父子 RDD 之间是否有 Shuffle, 大致有以下的方法

      • 如果是 Shuffle, 两个 RDD 的分区之间不是单纯的数据流动, 而是分发和复制
      • 一般 Shuffle 的子 RDD 的每个分区会依赖父 RDD 的多个分区

      先看是否一对一>是就是窄依赖,如果不是一对一,是多对一>不能确定,再继续判断

      但是这样判断其实不准确, 如果想分辨某个算子是否是窄依赖, 或者是否是宽依赖, 则还是要取决于具体的算子, 例如想看 cartesian 生成的是宽依赖还是窄依赖, 可以通过如下步骤

      1. 查看 map 算子生成的 RDD

        在这里插入图片描述

        在这里插入图片描述

        在这里插入图片描述

      2. 进去 RDD 查看 getDependence 方法

        在这里插入图片描述

        在这里插入图片描述

        在这里插入图片描述

        在这里插入图片描述
        724024159.png?origin_url=Untitled%2520180.png&pos_id=img-mqipe2i4-1704890196048)

    • 常见的窄依赖类型

      Dependency.scala 源码有。

      在这里插入图片描述

      • 一对一窄依赖

        其实 RDD 中默认的是 OneToOneDependency, 后被不同的 RDD 子类指定为其它的依赖类型, 常见的一对一依赖是 map 算子所产生的依赖, 例如 rddB = rddA.map(…)

        在这里插入图片描述

        • 每个分区之间一一对应, 所以叫做一对一窄依赖
      • Range 窄依赖

        Range 窄依赖其实也是一对一窄依赖, 但是保留了中间的分隔信息, 可以通过某个分区获取其父分区, 目前只有一个算子生成这种窄依赖, 就是 union 算子, 例如 rddC = rddA.union(rddB)

        在这里插入图片描述

        • rddC 其实就是 rddA 拼接 rddB 生成的, 所以 rddC 的 p5 和 p6 就是 rddB 的 p1 和 p2
        • 所以需要有方式获取到 rddC 的 p5 其父分区是谁, 于是就需要记录一下边界, 其它部分和一对一窄依赖一样
      • 多对一窄依赖

        多对一窄依赖其图形和 Shuffle 依赖非常相似, 所以在遇到的时候, 要注意其 RDD 之间是否有 Shuffle 过程, 比较容易让人困惑, 常见的多对一依赖就是重分区算子 coalesce, 例如 rddB = rddA.coalesce(2, shuffle = false), 但同时也要注意, 如果 shuffle = true 那就是完全不同的情况了

        在这里插入图片描述

        • 因为没有 Shuffle, 所以这是一个窄依赖
      • 再谈宽窄依赖的区别

        • 宽窄依赖的区别非常重要, 因为涉及了一件非常重要的事情: 如何计算 RDD ?
        • 宽窄依赖的核心区别是: 窄依赖的 RDD 可以放在一个 Task 中运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/312683.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置!

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置! 实际上,vue开发框架的时候,充分考虑到了前端开发人员可能会遇到的各种各样的情况,比如大家经常遇到的,数据类型的校验,再比如,默认…

Web安全测试基础

SQL注入 当下最常用的一个攻击手段,就是通过SQL命令插入到Web表单中或页面请求查询字符串中,最终达到欺骗服务器执行恶意的SQL语句的目的,SQL注入一旦成功,轻则直接绕开服务器验证,直接登录成功,重则将服务…

php多小区智慧物业管理系统源码带文字安装教程

多小区智慧物业管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 统计分析以小区为单位,统计如下数据:小区总栋数、小区总户数、小区总人数、 小区租户数量、小区每月收费金额统计、小区车位统计、小…

专业130+总400+哈尔滨工业大学803信号与系统和数字逻辑电路考研经验哈工大,电子信息,信息与通信工程,信通

今年专业课803信号与系统和数字逻辑130总分400如愿考上哈尔滨工业大学电子信息(信息与通信工程-信通),总结了一些各门课程复习心得,希望对大家复习有帮助。 数学一 资料选择: ①高数:张宇强化班 ②线性…

基于JavaWeb+BS架构+SpringBoot+Vue+Hadoop的物品租赁系统的设计与实现

基于JavaWebBS架构SpringBootVueHadoop的物品租赁系统的设计与实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 目  录 I 1绪 论 1 1.1开发背景 1 1.2开发目的与意义 1 1.2.1开发目…

【Redis】Redis 进阶

文章目录 1. BigKey1.1 MoreKey1.2 BigKey 2. 缓存双写一致性更新策略2.1 读缓存数据2.2 数据库和缓存一致性的更新策略2.3 canal 实现双写一致性 3. 进阶应用3.1 统计应用3.2 hyperloglog3.3 GEO3.4 bitmap 4. 布隆过滤器5. Redis 经典问题5.1 缓存预热5.2 缓存穿透5.3 缓存击…

C++ 类 对象

C 在 C 语言的基础上增加了面向对象编程,C 支持面向对象程序设计。类是 C 的核心特性,通常被称为用户定义的类型。 类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量&a…

Anaconda定制Python编程并打包

本文主要介绍如何使用Anaconda定制一个Python编程环境并打包,方便编程环境迁移。 文章参考 谢作如 邱奕盛两位老师的《为信息科技教学定制一个Python编程环境》 * 开发不同的项目需要不同的库(甚至不同版本),把所有的库安装到一…

Jenkins基础篇--添加用户和用户权限设置

添加用户 点击系统管理,点击管理用户,然后点击创建用户(Create User) 用户权限管理 点击系统管理,点击全局安全配置,找到授权策略,选择安全矩阵,配置好用户权限后,点击…

计算机网络-VLAN间通信

之前复习了VLAN的概念以及几个接口类型。VLAN在二层可以实现广播域的划分,VLAN间可以实现二层通信,但是不能实现三层通信,需要借助其它方式。 一、概述 实际网络部署中一般会将不同IP地址段划分到不同的VLAN。同VLAN且同网段的PC之间可直接进…

【Unity】Joystick Pack摇杆插件实现锁四向操作

Joystick Pack ​ 简介:一款Unity摇杆插件,非常轻量化 ​ 摇杆移动类型:圆形、横向、竖向 ​ 摇杆类型: Joystick描述Fixed固定位置Floating浮动操纵杆从用户触碰的地方开始,一直固定到触碰被释放。Dynamic动态操纵…

【设计模式】01-前言

23 Design Patterns implemented by C. 从本文开始,一系列的文章将揭开设计模式的神秘面纱。本篇博文是参考了《设计模式-可复用面向对象软件的基础》这本书,由于该书的引言 写的太好了,所以本文基本是对原书的摘抄。 0.前言 评估一个面向对…

孩子用什么样的灯对眼睛没有伤害?分享最合适孩子的护眼台灯

为人父母以后,孩子健康成长一定是摆放在首位的,随着孩子慢慢长大,步入更高的年级,作业课程也在随之增多。不少孩子哪怕夜色已经降临,仍就伏案在桌子上完成没有做完的功课,作为父母的我们不得不担心孩子的视…

Unity 工具 之 Azure 微软连续语音识别ASR的简单整理

Unity 工具 之 Azure 微软连续语音识别ASR的简单整理 目录 Unity 工具 之 Azure 微软连续语音识别ASR的简单整理 一、简单介绍 二、实现原理 三、注意实现 四、实现步骤 五、关键脚本 一、简单介绍 Unity 工具类,自己整理的一些游戏开发可能用到的模块&#x…

书客、明基、好视力护眼台灯大比拼,哪款更胜一筹?

在现代生活中,我们经常面对着各种电子屏幕,给眼睛造成了一定的压力,时间一长,会发现眼睛很疲劳。很多家长仔细观察,当孩子长时间处在不合适地灯光下玩耍、学习,会发现他们有揉眼的动作,这就是不…

Mac 使用nvm use命令无法切换node版本

解决方案:先卸载使用brew安装的node( 具体操作请移步使用brew卸载node),再使用nvm use命令切换node版本。 问题复现:使用nvm use命令显示切换成功,但是实际版本还是原来的node版本,应该是与bre…

HTTP响应码

1:1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码。 2:2xx(成功) 表示成功处理了请求的状态代码。 3:3xx(重定向) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。 4:4…

SpringBoot原理(@Conditional)—三种自动配置方法、步骤详解

简介:我们一直在说基于SpringBoot开发简单、快捷,但是总是不太清楚为什么会有这样的便利,对于开发人员来说我们不仅要知其然,还要知其所以然,这篇文章就是说明SpringBoot的底层原理,让读者对SpringBoot底层…

深入理解 Flink(五)Flink Standalone 集群启动源码剖析

前言 Flink 集群的逻辑概念: JobManager(StandaloneSessionClusterEntrypoint) TaskManager(TaskManagerRunner) Flink 集群的物理概念: ResourceManager(管理集群所有资源,管理集群所有从节点) TaskExecutor(管理从节点资源,接…