大数据实时阶段----【Spark02之ip地址查询、topN、PV、UV实战】

spark02_学习笔记

1、目标

  • 1、掌握RDD的底层原理
  • 2、掌握RDD的常用的算子操作
  • 3、掌握RDD的宽窄依赖
  • 4、掌握RDD的缓存机制
  • 5、掌握划分stage
  • 6、掌握spark任务运行架构和调度流程

2、RDD概述

2.1 什么是RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。

Dataset:它是一个集合,集合里面有很多个元素
Distributed:rdd中的数据是进行了分布式存储,后期方便于进行分布式计算。
Resilient:弹性,可以意味着rdd的数据可以保存在内存或者是磁盘中。
2.2 RDD的五大属性

在这里插入图片描述

(1) A list of partitions
一个分区列表
	它表示一个rdd中有很多个分区,后期spark任务的计算是以分区为单位进行计算。一个分区就对应上一个task线程。
	val rdd=sc.textFile(文件)
	该文件的block个数小于等于2,这个时候rdd的分区数就是2
	该文件的block个数大于2,  这个时候rdd的分区数就跟block个数相等
	
	

(2) A function for computing each split
  作用在每一个分区中的函数
  var rdd2=rdd1.map(x=>(x,1))


(3) A list of dependencies on other RDDs
  一个RDD会依赖于其他多个RDD,这里就涉及到RDD与RDD之间的依赖关系,后期spark任务的容错机制就是根据这个特性而来。
   var rdd2=rdd1.map(x=>(x,1))

(4) Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
    (可选性) 一个分区函数,针对于一个kv类型的RDD才会有分区函数(必须要产生shuffle)。对于不是kv类型的RDD,它的分区函数是None.  spark提供了2种shuffle机制,第一种默认值:hashPartitioner ------->  key.hashcode % 分区数=分区号,
    第二种RangePartitioner: 基于一个范围的分区策略。


(5) Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
    (可选性)   一个优先的数据分区列表,这里就涉及到数据本地性和数据位置最优(指的是哪个节点上有数据 , 就优先考虑哪个节点)。
    spark后期再进行任务分配的时候,会优先考虑存有数据的worker节点来进行任务的计算。

当任务运行到reduceByKey的时候 , 此时产生了shuffle , 因此每个shuffle中的内容有可能来自于多个分区 .

2.3 创建RDD
  • 1、通过一个已经存在的scala集合去构建
    • val rdd1=sc.parallelize(List(1,2,3,4),2)
  • 2、通过加载外部的数据源去构建
    • val rdd2=sc.textFile("/words.txt")
  • 3、通过一个已经存在的rdd去构建
    • val rdd3=rdd2.flatMap(x=>x.split(" "))

3、RDD中算子操作

  • rdd中算子一共可以分为2类

  • transformation(转换)

    • 它是一个转换,可以实现把一个rdd转换生成一个新的rdd,它不会立即触发任务的运行,它是延迟加载。
      • 它只是记录下作用在rdd的上转换操作
      • 比如
        • flatMap/map/reduceByKey/sortBy
  • action (动作)

    • 它会触发任务的真正运行

      • 比如

        • collect/saveAsTextFile
  • 常用的Transformation:
转换 含义
map(func) 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
filter(func) 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
flatMap(func) 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
mapPartitions(func) 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func) 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是 (Int, Interator[T]) => Iterator[U]
union(otherDataset) 对源RDD和参数RDD求并集后返回一个新的RDD
intersection(otherDataset) 对源RDD和参数RDD求交集后返回一个新的RDD
distinct([numTasks])) 对源RDD进行去重后返回一个新的RDD
groupByKey([numTasks]) 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD
reduceByKey(func, [numTasks]) 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置
sortByKey([ascending], [numTasks]) 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks]) 与sortByKey类似,但是更灵活
join(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
cogroup(otherDataset, [numTasks]) 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
coalesce(numPartitions) 减少 RDD 的分区数到指定值。
repartition(numPartitions) 重新给 RDD 分区
repartitionAndSortWithinPartitions(partitioner) 重新给 RDD 分区,并且每个分区内以记录的 key 排序
  • 常用Action类
动作 含义
reduce(func) reduce将RDD中元素前两个传给输入函数,产生一个新的return值,新产生的return值与RDD中下一个元素(第三个元素)组成两个元素,再被传给输入函数,直到最后只有一个值为止。
collect() 在驱动程序中,以数组的形式返回数据集的所有元素
count() 返回RDD的元素个数
first() 返回RDD的第一个元素(类似于take(1))
take(n) 返回一个由数据集的前n个元素组成的数组
takeOrdered(n, [ordering]) 返回自然顺序或者自定义顺序的前 n 个元素
saveAsTextFile(path) 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
saveAsSequenceFile(path) 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
saveAsObjectFile(path) 将数据集的元素,以 Java 序列化的方式保存到指定的目录下
countByKey() 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func) 在数据集的每一个元素上,运行函数func
foreachPartition(func) 在数据集的每一个分区上,运行函数func
  • RDD常用的算子操作

    Spark Rdd的所有算子操作,请见《sparkRDD函数详解

    启动spark-shell 进行测试:

    spark-shell --master spark://node1:7077

    也可本地操作spark-shell --master local[2]

    练习1:map、filter

    //通过并行化生成rdd
    val rdd1 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10))
    
    //对rdd1里的每一个元素乘2然后排序
    val rdd2 = rdd1.map(_ * 2).sortBy(x => x, true)
    
    //过滤出大于等于5的元素
    val rdd3 = rdd2.filter(_ >= 5)
    
    //将元素以数组的方式在客户端显示
    rdd3.collect
    

    练习2:flatMap

    val rdd1 = sc.parallelize(Array("a b c", "d e f", "h i j"))
    //将rdd1里面的每一个元素先切分在压平
    
    val rdd2 = rdd1.flatMap(_.split(" "))
    rdd2.collect
    

    练习3:交集、并集

    val rdd1 = sc.parallelize(List(5, 6, 4, 3))
    val rdd2 = sc.parallelize(List(1, 2, 3, 4))
    
    //求并集
    val rdd3 = rdd1.union(rdd2)
    
    //求交集
    val rdd4 = rdd1.intersection(rdd2)
    
    //去重
    rdd3.distinct.collect
    rdd4.collect
    

    练习4:join、groupByKey

    val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2)))
    
    //求join
    val rdd3 = rdd1.join(rdd2)
    rdd3.collect
    
    //求并集
    val rdd4 = rdd1 union rdd2
    rdd4.collect
    
    //按key进行分组
    val rdd5=rdd4.groupByKey
    rdd5.collect
    

    练习5:cogroup

    val rdd1 = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("jim", 2)))
    //cogroup
    val rdd3 = rdd1.cogroup(rdd2)
    //注意cogroup与groupByKey的区别
    rdd3.collect
    

    练习6:reduce

    val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5))
    //reduce聚合
    val rdd2 = rdd1.reduce(_ + _)
    rdd2.collect
    

    练习7:reduceByKey、sortByKey

    val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2),  ("shuke", 1)))
    val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5)))
    val rdd3 = rdd1.union(rdd2)
    //按key进行聚合
    val rdd4 = rdd3.reduceByKey(_ + _)
    rdd4.collect
    
    //按value的降序排序
    val rdd5 = rdd4.map(t => (t._2, t._1)).sortByKey(false).map(t => (t._2, t._1))
    rdd5.collect
    

    练习8:repartition、coalesce

    val rdd1 = sc.parallelize(1 to 10,3)
    //利用repartition改变rdd1分区数
    //减少分区
    rdd1.repartition(2).partitions.size
    //增加分区
    rdd1.repartition(4).partitions.size
    //利用coalesce改变rdd1分区数
    //减少分区
    rdd1.coalesce(2).partitions.size
    

注意:repartition可以增加和减少rdd中的分区数 , 可用于小文件的整合 , 将多个小文件放在一个分区中,coalesce只能减少rdd分区数,增加rdd分区数不会生效。

4、通过spark实现点击流日志分析案例

4.1 统计PV
package cn.itcast.rdd

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

//todo:利用spark实现点击流日志数据分析----------------PV
object PV {
  def main(args: Array[String]): Unit = {
       //1、创建SparkConf
      val sparkConf: SparkConf = new SparkConf().setAppName("PV").setMaster("local[2]")

      //2、创建SparkContext
      val sc = new SparkContext(sparkConf)
      sc.setLogLevel("warn")

     //3、读取数据文件
     val data: RDD[String] = sc.textFile("E:\\data\\access.log")

    //4、统计pv
    val pv: Long = data.count()
    println("PV:"+pv)

    //5、关闭
    sc.stop()

  }
}
4.2 统计UV
package cn.itcast.rdd

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

//todo:利用spark实现点击流日志数据分析----------------UV
object UV {
  def main(args: Array[String]): Unit = {
    //1、创建SparkConf
    val sparkConf: SparkConf = new SparkConf().setAppName("UV").setMaster("local[2]")

    //2、创建SparkContext
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、读取数据文件
    val data: RDD[String] = sc.textFile("E:\\data\\access.log")

    //4、切分每一行,获取所有的ip地址
      val ips: RDD[String] = data.map(x=>x.split(" ")(0))

    //5、根据ip地址去重
      val distinctRDD: RDD[String] = ips.distinct()

    //6、统计UV
       val uv: Long = distinctRDD.count()
      println("UV:"+uv)

    //7、关闭
      sc.stop()
  }
}

4.3 统计TopN
package cn.itcast.rdd

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

//todo:利用spark实现点击流日志数据分析----------------TopN(把访问url出现次数最多的前N位获取得到)
object TopN {
  def main(args: Array[String]): Unit = {
    //1、创建SparkConf
    val sparkConf: SparkConf = new SparkConf().setAppName("TopN").setMaster("local[2]")

    //2、创建SparkContext
    val sc = new SparkContext(sparkConf)
    sc.setLogLevel("warn")

    //3、读取数据文件
    val data: RDD[String] = sc.textFile("E:\\data\\access.log")

    //4、先过滤出丢失的字段的记录
    val urlAndOne: RDD[(String, Int)] = data.filter(x=>x.split(" ").length >10).map(x=>x.split(" ")(10)).map(x=>(x,1))

   //5、相同url出现的1累加
   val result: RDD[(String, Int)] = urlAndOne.reduceByKey(_+_)
      
   //将输出的结果去掉"-"的项
   val fresult: RDD[(String, Int)] = result.filter(x=>x._1!="\"-\"")

   //6、按照url出现的次数降序
   val sortedRDD: RDD[(String, Int)] = fresult.sortBy(x=>x._2,false)

    //7、取出出现次数最多的前5位
    val finalResult: Array[(String, Int)] = sortedRDD.take(5)

      finalResult.foreach(println)

    //8、关闭
    sc.stop()

  }
}

5、通过spark实现ip地址查询

package cn.itcast.rdd

import java.sql.{Connection, DriverManager, PreparedStatement}

import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

//todo:利用spark来实现ip地址查询
object Iplocation {

   //把String类型的ip转换成Long类型的数字   192.168.200.100
  def ip2Long(ip: String): Long ={
      val ips: Array[String] = ip.split("\\.")
      var ipNum:Long=0L

      for(i <- ips){
         ipNum= i.toLong  |  ipNum << 8
      }

    ipNum

  }

  def binarySearch(ipNum: Long, broadCastValue: Array[(String, String, String, String)]): Int = {
      var start=0
      var end=broadCastValue.length-1

      while(start <= end){
           val middle=(start+end)/2

        if(ipNum >=broadCastValue(middle)._1.toLong && ipNum <= broadCastValue(middle)._2.toLong){
            return middle
        }

        if(ipNum < broadCastValue(middle)._1.toLong){
           end=middle-1
        }

        if(ipNum >broadCastValue(middle)._2.toLong){
          start=middle+1
        }

      }
    -1
  }

   //把数据写入到mysql表中
  def data2mysql(iter:Iterator[((String,String), Int)])= {
    //定义数据库连接
      var conn:Connection=null
    //定义PrepareStatement
      var ps:PreparedStatement=null

    //定义sql语句
      val sql="insert into iplocation(longitude,latitude,total_count)  values(?,?,?)"

    //获取数据库连接
    conn= DriverManager.getConnection("jdbc:mysql://192.168.200.100:3306/spark","root","123456")
    //获取PrepareStatement
    ps=conn.prepareStatement(sql)

    //遍历迭代器
      try {
        iter.foreach(line => {
          //给?占位符赋值
          ps.setString(1, line._1._1)
          ps.setString(2, line._1._2)
          ps.setLong(3, line._2)

          ps.execute()
        })
      } catch {
        case e:Exception => println(e)
      } finally {

        if(ps!=null){
           ps.close()
        }

        if(conn!=null){
          conn.close()
        }

      }

  }

  def main(args: Array[String]): Unit = {
       //1、创建SparkConf
      val sparkConf: SparkConf = new SparkConf().setAppName("Iplocation").setMaster("local[2]")
      //2、创建SparkContext
      val sc = new SparkContext(sparkConf)
      sc.setLogLevel("warn")

     //3、读取城市ip信息数据文件   获取ip开始数字,ip结束数字,经度,维度
       val city_ip_rdd: RDD[(String, String, String, String)] = sc.textFile("E:\\data\\ip.txt").map(x=>x.split("\\|")).map(x=>(x(2),x(3),x(x.length-2),x(x.length-1)))

      //把城市ip信息数据通过广播变量下发到每一个worker节点  ,broadcast方法中需要给定rdd的真实数据,不能够直接把rdd传进去
       val city_ip_broadcast: Broadcast[Array[(String, String, String, String)]] = sc.broadcast(city_ip_rdd.collect())


    //4、读取运营商日志数据  获取所有的ip地址
        val ips: RDD[String] = sc.textFile("E:\\data\\20090121000132.394251.http.format").map(x=>x.split("\\|")(1))

    //5、遍历ips,获取每一个ip,然后把ip地址转换成Long类型,后期去匹配
    val result: RDD[((String, String), Int)] = ips.mapPartitions(iter => {
      //获取广播变量的值
      val broadCastValue: Array[(String, String, String, String)] = city_ip_broadcast.value
      //遍历迭代器,获取每一个ip地址
      iter.map(ip => {
        //把ip地址转换成Long类型数字
        val ipNum: Long = ip2Long(ip)

        //通过二分查询获取ipNum在broadCastValue数组中的下标
        val index: Int = binarySearch(ipNum, broadCastValue)

        //获取下标对应的元素
        val value: (String, String, String, String) = broadCastValue(index)

        //返回结果数据  ((经度,维度),1)
        ((value._3, value._4), 1)
      })

    })
    //6、把相同经度和维度出现的1累加
    val finalResult: RDD[((String, String), Int)] = result.reduceByKey(_+_)

    //7、打印结果数据
      finalResult.foreach(println)

     //把结果数据写入到mysql表中
      finalResult.foreachPartition(data2mysql)

    //8、关闭
    sc.stop()
  }
}

扩展 : 广播变量图解

在这里插入图片描述

广播变量极大地减少了内存的开销 , 保证了程序的正常运行

6、RDD的依赖关系

在这里插入图片描述

  • rdd与rdd之间有依赖关系

    • 窄依赖

      窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用
      总结:窄依赖我们形象的比喻为独生子女
      
      窄依赖不会产生shuffle
      flatMap filter map....
      
    • 宽依赖

      宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition
      总结:宽依赖我们形象的比喻为超生 
      
      宽依赖会产生shuffle
      reduceByKey  groupByKey  sortBy....
      

7、lineage(血统)

	rdd后期会进行大量的转换操作,我们把rdd的这些操作行为记录下来,记录下来的信息我们就称为lineage(血统)
血统好处:
	当前某一个rdd的分区数据丢失了,可以通过血统这一层关系来重新计算恢复得到。这里spark的任务容错机制就是根据血统而来。

8、RDD的缓存机制

8.1 RDD的缓存是什么
可以把一个rdd的结果数据进行缓存,后续有其他的job需要依赖于前面rdd的结果数据,这个时候可以直接从缓存中获取得到,避免重复计算。
8.2 如何设置缓存
rdd中提供了设置缓存的2种方式

cache:默认是将数据缓存在内存中,其本质是调用persist方法
设置方式 : rdd.cache
persist:可以把数据缓存内存或者是磁盘中,它里面可以设置丰富的缓存级别,这些缓存级别都封装
在一个object 中,这个object的名称Storagelevel
设置方式 : rdd.persist(org.apache.spark.storage.StorageLevel)

以上这个2个方法并不是调用之后就立即执行,后续是需要一个action操作,才会触发缓存真正执行。
8.3 如何清除缓存
调用rdd的unpersist 清除缓存数据。

 手动清除缓存:rdd1.unpersist(true) 
 自动清除:对于程序来说,如果我们设置了缓存,后期程序结束了,它会自动清除
8.4 什么时候设置缓存
  • 1、一个rdd后期被使用了多次

    val rdd2=rdd1.flatMap(_.split(" "))
    val rdd3=rdd1.map(x=>(x,1))
    
    上面的rdd1被使用了多次,每一次使用都需要先把rdd1的结果数据先计算一下,这个时候就可以对rdd1设置缓存,避免后续的rdd需要前面的结果。
    
  • 2、某一个rdd的数据来之不易

    val rdd2=rdd1.flatMap(_.split(" ")).map().xxxxx.xxxxxxx.xxxxxxx.xxxxxxx.xxx.xxx
    

9、DAG有向无环图和划分stage

9.1 什么是DAG
DAG就是按照rdd的一系列操作最后生成了一个有方向无闭环的图,这个图我们就称为DAG有向无环图。按照操作逻辑划分成不同的stage(不同的调度阶段)

在这里插入图片描述

(1) 为什么要划分stage?

​ 在每一个stage中都是窄依赖 , 并没有宽依赖 , spark的任务task就是以分区为单位 , 这些task都是可以并行的运行 , 互不影响

(2) 如何划分stage? 划分stage的依据就是宽依赖

  1. 从最后一个rdd往前推 , 先创建一个stage , 然后把最后一个rdd加入到该stage中 , 它就是最后一个stage
  2. 如果遇到了窄依赖 , 就把该rdd加入到本stage中 , 如果遇到了宽依赖 , 就从宽依赖切开 , 最后一个stage也就结束了
  3. 重新创建一个新的stage , 按照第二步的操作 , 继续往前推 , 一直推到开始 , 整个划分stage也就结束了

(3) stage的内部逻辑

每一个stage中都有很多可以并行运行的task , 这些task被封装在一个taskSet集合中


扩展 : rdd与rdd之间有依赖关系 , stage与stage之间也有依赖关系 , 比如前面的stage中的task先运行 , 后面的stage中的task后运行 , 也就是说stage中输入数据是前面stage输出结果

后期开发好的代码中 , 一个action就是一个job , 一个application中包括了客户端的所有代码 , 也就是说一个application中很多个job , 一个job中会存在rdd的一系列操作 , 一个job会生成一个DAG有向无环图 , 一个job中有可能有多个宽依赖 , 按照宽依赖切分 , 这个时候也就意味着一个job中有很多个stage , 每一个stage内部都有很多可以并行跑的task

10、spark任务调度

在这里插入图片描述

(1)Driver会运行客户端main方法中的代码,代码就会构建SparkContext对象,在构建SparkContext对象中,会创建DAGScheduler和TaskScheduler,然后按照rdd一系列的操作生成DAG有向无环图。最后把DAG有向无环图提交给DAGScheduler。

(2)DAGScheduler拿到DAG有向无环图后,按照宽依赖进行stage的划分,这个时候会产生很多个stage,每一个stage中都有很多可以并行运行的task,把每一个stage中这些task封装在一个taskSet集合中,最后提交给TaskScheduler。

(3)TaskScheduler拿到taskSet集合后,依次遍历每一个task,最后提交给worker节点的exectuor进程中。task就以线程的方式运行在worker节点的executor进程中。

11、spark的容错机制之checkpoint

11.1 什么是checkpoint
对rdd设置缓存有2种方式:
(1)cache:默认是把数据缓存在内存中,后续操作起来速度比较快,但是由于进程或者是服务器挂掉了,这个时候内存中的数据肯定是丢失,也就是说cache不是非常安全,数据丢失的概率比较大。

(2)persist:有丰富的缓存级别,可以把数据缓存在磁盘中,然后需要用到该数据,可以进行磁盘io操作获取得到,这一点比cache速度会慢点,但是比cache安全点,这里同样也有数据丢失的可能性(磁盘损坏、系统管理员由于误操作把本地数据清除掉了)

checkpoint:它是提供了一个相对而言更加可靠的持久化数据的方式,它可以把rdd的数据写入到分布式文件系统(HDFS)去保存,利用了hdfs高可靠,多个副本机制最大程度保证数据不丢失。
11.2 如何使用checkpoint
  • 1、通过sparkContext对象设置checkpoint目录,用于保存rdd的数据
    • sc.setCheckpointDir("/ck2018")
  • 2、对需要持久化的rdd调用一个方法checkpoint方法
    • val rdd1=sc.textFile("/words.txt")
    • rdd1.checkpoint
  • 3、后续需要有个action操作,触发checkpoint的执行
    • rdd1.collect
11.3 cache/persist/checkpoint区别
cache和persist:
	这2个方法都可以将rdd的数据进行缓存,后续都要有一个action操作,才会触发缓存任务的执行,它不会改变rdd的血统。整个程序结束之后,这些缓存数据自己被清除了。
	
	
checkpoint:
	可以把数据持久化到hdfs上,这个时候先rdd.checkpoint操作,然后也需要一个action。
	一个action操作就是一个job,在这里首先它会执行action这个job,执行完成之后,它会开启一个新的job来执行checkpoint操作,也就是说在这里比cache和persist多了一个job。它会改变rdd的血统。
11.4 数据丢失之后的恢复顺序
1、首先看一写有没有设置cache,如果有,直接从cache获取得到					     
2、如果没有cache,看一下有没有做checkpoint,如果有就直接从checkpoint获取得到	    
3,如果没有checkpoint,利用血统这层关系来重新计算恢复得到。

12、spark的运行架构

在这里插入图片描述

  1.   构建Spark Application的运行环境(启动SparkContext),SparkContext向资源管理器(可以是Standalone、Mesos或YARN)注册并申请运行Executor资源;
    
  2.  资源管理器分配Executor资源并启动Executor,Executor运行情况将随着心跳发送到资源管理器上;
    
  3. SparkContext构建成DAG图,将DAG图分解成Stage,并把Taskset发送给Task Scheduler。Executor向SparkContext申请Task,Task Scheduler将Task发放给Executor运行同时SparkContext将应用程序代码发放给Executor。
    
  4. Task在Executor上运行,运行完毕释放所有资源。
    

13. Spark运行架构特点

Spark运行架构特点:

  • 每个Application获取专属的executor进程,该进程在Application期间一直驻留,并以多线程方式运行tasks。
  • Spark任务与资源管理器无关,只要能够获取executor进程,并能保持相互通信就可以了。
  • 提交SparkContext的Client应该靠近Worker节点(运行Executor的节点),最好是在同一个Rack里,因为Spark程序运行过程中SparkContext和Executor之间有大量的信息交换;如果想在远程集群中运行,最好使用RPC将SparkContext提交给集群,不要远离Worker运行SparkContext。
  • Task采用了数据本地性和推测执行的优化机制。

猜你喜欢

转载自blog.csdn.net/CoderBoom/article/details/84780108