SparkCore

SparkCore

1.RDD的依赖关系

1.1RDD的依赖

RDD和它依赖的父RDD的关系有两种不同的类型,即窄依赖(narrow dependency宽依赖(wide dependency

如何区分宽窄依赖

窄依赖:父RDD的一个分区只会被子RDD的一个分区依赖

宽依赖:父RDD的一个分区会被子RDD的多个分区依赖(涉及到shuffle)

 

●面试题:

子RDD的一个分区依赖多个父RDD是宽依赖还是窄依赖?

不能确定,也就是宽窄依赖的划分依据是父RDD的一个分区是否被子RDD的多个分区所依赖,是,就是宽依赖,或者从shuffle的角度去判断,有shuffle就是宽依赖

为什么要设计宽窄依赖

1.对于窄依赖

Spark可以并行计算

如果有一个分区数据丢失,只需要从父RDD的对应1个分区重新计算即可,不需要重新计算整个任务,提高容错。

2.对于宽依赖

是划分Stage的依据

血统

RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

spark任务组件

  • 1-一个Application就是包含多个Job,调用一次action产生的就是一次job

  • 2-一个Job也就是一个DAG包含有很多Stages

  • 4-Stages是根据宽窄依赖划分的,出现宽依赖就可以将Stages进行划分,因为窄依赖可以在Stage内部完成

  • 5-一个Stages包含有很多TaskSet

  • 6-一个TaskSet就是一个RDD,一个RDD有多个分区,每个分区都是由Task线程执行

  • 7-Task是Spark执行任务的最小线程的单元--Task(worker节点的Executor的Task线程中)

 

RDD的缓存

Spark速度非常快的原因之一,就是在不同操作中可以在内存中持久化或者缓存数据集。当持久化某个RDD后,每一个节点都将把计算分区结果保存在内存中,对此RDD或衍生出的RDD进行的其他动作中重用。这使得后续的动作变得更加迅速。RDD相关的持久化和缓存,是Spark最重要的特征之一。可以说,缓存是Spark构建迭代式算法和快速交互式查询的关键。

 

RDD缓存方式

RDD通过persist方法或cache方法可以将前面的计算结果缓存,但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

7.3RDD cache使用

val data = sc.textFile(“hdfs://nn:8020/input”)

data.cache() //实际上是data.persist(StorageLevel.MEMORY_ONLY)

//data.persist(StorageLevel.DISK_ONLY_2)

 

7.4cache和persist的区别

/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */

def cache(): this.type = persist()

 

说明是cache()调用了persist(), 想要知道二者的不同还需要看一下persist函数:

/** Persist this RDD with the default storage level (`MEMORY_ONLY`). */

def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)

2.RDD创建的分区详解

makeRDD()或者parallieise()默认分区数

textFile()默认分区数

3.checkPoint机制

Checkpoint的产生就是为了相对而言更加可靠的持久化数据,在Checkpoint的时候可以指定把数据放在本地,并且是多副本的方式,但是在生产环境下是放在HDFS上,这就天然的借助了HDFS高容错、高可靠的特征来完成了最大化的可靠的持久化数据的方式

  • Spark读取缓存或checpoint的顺序?

    • 1-Spark会从cache机制从内存中读取RDD数据

    • 2-如果数据没有读到,会使用checkpoint机制读取数据。此时如果没有checkpoint机制

    • 3-那么就需要找到父RDD重新计算数据了(Spark的RDD依赖关系,重建依赖关系)

    • 总计:checkpoint是个很重要的容错机制。

  • SPark提供的缓存机制和checkpoint机制的区别

    • 1-Persist和Cache只是将数据保存在block块中,但是lineage血统的关系是不变的

    • 2-CheckPoint执行完毕后,rdd已经没有依赖关系的存在,切断了依赖关系,Checpoint设置的目录就会保存依赖关系的RDD,checkpoint之后的RDD的依赖改变了。

    • 3-可靠性讲---checpoint更加可靠

  • Spark的缓存或检查点checkpoint机制什么时候使用?

    • 一定在RDD的依赖关系或血缘关系非常长的时候,建议使用缓存或检查点机制

checkpoint原理机制

当RDD使用cache机制从内存中读取数据,如果数据没有读到,会使用checkpoint机制读取数据。此时如果没有checkpoint机制,那么就需要找到父RDD重新计算数据了,因此checkpoint是个很重要的容错机制。checkpoint就是对于一个RDD chain(链)如果后面需要反复使用某些中间结果RDD,可能因为一些故障导致该中间数据丢失,那么就可以针对该RDD启动checkpoint机制,使用checkpoint首先需要调用sparkContext的setCheckpointDir方法,设置一个容错文件系统目录,比如hdfs,然后对RDD调用checkpoint方法。之后在RDD所处的job运行结束后,会启动一个单独的job来将checkpoint过的数据写入之前设置的文件系统持久化,进行高可用。所以后面的计算在使用该RDD时,如果数据丢失了,但是还是可以从它的checkpoint中读取数据,不需要重新计算。

persist或者cache与checkpoint的区别在于,前者持久化只是将数据保存在BlockManager中但是其lineage是不变的,但是后者checkpoint执行完后,rdd已经没有依赖RDD,只有一个checkpointRDD,checkpoint之后,RDD的lineage就改变了。persist或者cache持久化的数据丢失的可能性更大,因为可能磁盘或内存被清理,但是checkpoint的数据通常保存到hdfs上,放在了高容错文件系统。

 

课堂图解总结:

一个application里面包含多个job,调用一次action算子,就是一个job。如果rdd的数据要进行复用,可以将rdd的数据进行缓存,使用cache或者persist将数据进行缓存

内存里面的数据可能会过期

persist将数据缓存到本地磁盘文件,磁盘文件有可能会被删掉

对于一些比较珍贵的rdd里面的数据可以使用checkpoint将数据持久化保存起来.

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("Ip_address").setMaster("local[*]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    sc.setCheckpointDir("E:\\WorkSpace\\IdeaWS\\SparkTest\\day02_SaprkCore\\src\\main\\resources\\checkDir")

    val rdd1: RDD[String] = sc.textFile("E:\\WorkSpace\\IdeaWS\\SparkTest\\day02_SaprkCore\\src\\main\\resources\\access.log")

    val rdd2: RDD[Array[String]] = rdd1.map(_.split(" "))
    rdd2.checkpoint()
    rdd2.foreach(println(_))

  }

4.spark任务调度

Spark的任务调度流程:

第一步:客户端提交jar包,提交一个application

第二步:根据提交的application以及rdd的依赖关系和血统lineage划分DAG

第三步:DAG送给DAGScheduler

第四步:DAGScheduler划分stage,划分taskSet

第五步:将taskSet送给taskScheduler

第六步:taskScheduler接收到taskSet

第七步:taskScheduler将接收到的taskset分解成为一个个的task,等待将task送给executor去执行

以上七部:都是Driver程序负责的职责,Driver程序一般都是运行在worker里面,使用cluster模式

第八步:将分解之后的task与executor进行通信,将task放到executor里面去执行

●为什么要划分Stage? --并行计算

一个复杂的业务逻辑如果有shuffle,那么就意味着前面阶段产生结果后,才能执行下一个阶段,即下一个阶段的计算要依赖上一个阶段的数据。那么我们按照shuffle进行划分(也就是按照宽依赖就行划分),就可以将一个DAG划分成多个Stage/阶段,在同一个Stage中,会有多个算子操作,可以形成一个pipeline流水线,流水线内的多个平行的分区可以并行执行

●如何划分DAG的stage

对于窄依赖,partition的转换处理在stage中完成计算,不划分(将窄依赖尽量放在在同一个stage中,可以实现流水线计算)

对于宽依赖,由于有shuffle的存在,只能在父RDD处理完成后,才能开始接下来的计算,也就是说需要要划分stage

 

●总结

Spark会根据shuffle/宽依赖使用回溯算法来对DAG进行Stage划分,从后往前,遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到当前的stage/阶段中

DAGScheduler

DAGScheduler对DAG有向无环图进行Stage划分。

记录哪个RDD或者 Stage 输出被物化(缓存),通常在一个复杂的shuffle之后,通常物化一下(cache、persist),方便之后的计算。

重新提交shuffle输出丢失的stage(stage内部计算出错)给TaskScheduler

将 Taskset 传给底层调度器

– spark-cluster TaskScheduler

– yarn-cluster YarnClusterScheduler

– yarn-client YarnClientClusterScheduler

TaskScheduler

(1)为每一个TaskSet构建一个TaskSetManager 实例管理这个TaskSet 的生命周期

(2)数据本地性决定每个Task最佳位置

(3)提交 taskset( 一组task) 到集群运行并监控

(4)推测执行,碰到计算缓慢任务需要放到别的节点上重试

(5)重新提交Shuffle输出丢失的Stage给DAGScheduler

5.spark运行架构

  • 细化架构

  • 1.Application:指的是*用户编写的Spark应用程序/代码*,包含了Driver功能代码和分布在集群中多个节点上运行的Executor代码。

    2.Driver:Spark中的Driver即运*行上述Application的Main()函数*并且创建SparkContext,SparkContext负责和ClusterManager通信,进行资源的申请、任务的分配和监控等

    3.Cluster Manager:指的是在集群上获取资源的外部服务,Standalone模式下由Master负责,Yarn模式下ResourceManager负责;

    4.Executor:是运行在工作节点Worker上的进程,负责运行任务,并为应用程序存储数据,是执行分区计算任务的进程;

    5.RDD:Resilient Distributed Dataset弹性分布式数据集,是分布式内存的一个抽象概念;

    6.DAG:Directed Acyclic Graph有向无环图,反映RDD之间的依赖关系和执行流程;

    7.Job:作业,按照DAG执行就是一个作业;Job==DAG

    8.Stage:阶段,是作业的基本调度单位,同一个Stage中的Task可以并行执行,多个Task组成TaskSet任务集

    9.Task:任务,运行在Executor上的工作单元,一个 Task 计算一个分区,包括pipline上的一系列操作

Spark运行基本流程

1.当一个Spark应用被提交时,首先需要为这个Spark Application构建基本的运行环境,即由任务控制节点(Driver)创建一个SparkContext,

2.SparkContext向资源管理器注册并申请运行Executor资源;

3.资源管理器为Executor分配资源并启动Executor进程,Executor运行情况将随着心跳发送到资源管理器上;

4.SparkContext根据RDD的依赖关系构建成DAG图,并提交给DAGScheduler进行解析划分成Stage,并把该Stage中的Task组成的Taskset发送给TaskScheduler。

5.TaskScheduler将Task发放给Executor运行,同时SparkContext将应用程序代码发放给Executor。

6.Executor将Task丢入到线程池中执行,把执行结果反馈给任务调度器,然后反馈给DAG调度器,运行完毕后写入数据并释放所有资源。

6.累加器

通常在向 Spark 传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱动器driver程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。这时使用累加器可以实现我们想要的效果。

val xx: Accumulator[Int] = sc.accumulator(0)

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("Ip_address").setMaster("local[*]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")

    val seq: Seq[Int] = Seq(1,2,3)
    var count = 0
    seq.foreach(x=>count+=x)
    println(count)
    println("==================================")

    var count1 = 0
    val rdd1: RDD[Int] = sc.parallelize(seq)
    rdd1.foreach(count1+=_)
    println(count1)

    println("=================================================")
    val count2: Accumulator[Int] = sc.accumulator(0)
    rdd1.foreach(count2+=_)
    println(count2)
    val acc: DoubleAccumulator = sc.doubleAccumulator("acc")
  }

 7.广播变量

广播变量允许程序员将一个只读的变量缓存在每台机器上,而不用在任务之间传递变量。广播变量可被用于有效地给每个节点一个大输入数据集的副本。

 

假如我们要共享的变量map,1M

在默认的,task执行的算子中,使用了外部的变量,每个task都会获取一份变量的副本

广播,Broadcast,将大变量广播出去。而不是直接使用

广播变量的好处,不是每个task一份变量副本,而是变成每个节点的executor才一份副本。这样的话,就可以让变量产生的副本大大减少。

广播变量,初始的时候,就在Drvier上有一份副本。task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝试获取变量副本;如果本地没有,BlockManager,也许会从远程的Driver上面去获取变量副本;也有可能从距离比较近的其他节点的Executor的BlockManager上去获取,并保存在本地的BlockManager中;BlockManager负责管理某个Executor对应的内存和磁盘上的数据,此后这个executor上的task,都会直接使用本地的BlockManager中的副本。

val Broadcast: Broadcast[Array[(String, String, String, String)]] = sc.broadcast(ip_info.collect())

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值