Spark3.0版本--chapter2.7--RDD持久化

RDD持久化知识总结:

RDD Cache总结知识要点:
一.RDD cache缓存有两个方法:cache和persist;persist更底层,因名字不好记,因此使用cache包一下
在使用时,默认会序列化形式缓存在JVM中(堆内存中),但是两个方法被调用不会立即缓存,需要有action算子才会执行缓存
简记:cache底层调用persist(),并且缓存级别默认选用MEMORY_ONLY,且cache()方法不支持缓存方式,persist支持改缓存级别。

二.为什么两个方法被调用时不会立即缓存?
因为spark中,这两个方法特性:懒加载,因此我们在使用时,建议放在action算子之前。

三.RDD缓存方式
内存	磁盘	内存和磁盘
优先存内存,内存存不下考虑是否能存磁盘。因实际代码而定。

四.算子涉及到Shuffle自带缓存功能。

2.7.1RDD Cache 缓存

RDD通过Cache或者Persist方法将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在JVM的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的action算子时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
dxy-RDD缓存
说明:
0)创建包名:com.atguigu.cache
1)代码实现
准备文件1.txt

hello spark
hello spark
hello scala
package com.atguigu.cache

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

/**
 * @author dxy
 * @date 2021/2/24 10:19
 */
object cache01 {
  def main(args: Array[String]): Unit = {
    //TODO 1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //TODO 2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    val lineRDD: RDD[String] = sc.textFile("D:\\DevelopmentTools\\spark\\SparkCoreTest1109\\input\\1.txt")

    val wordRDD: RDD[String] = lineRDD.flatMap(_.split(" "))

    val word2OneRDD: RDD[(String, Int)] = wordRDD.map(
      word => {
        println("******************************")
        (word, 1)
      }
    )

    //word2OneRDD在缓存前  查看血缘关系
    println(word2OneRDD.toDebugString)

    /**
     * spark缓存方法有两个
     * 1.cache 底层调用的就是persist(),并且缓存级别默认选用的是MEMORY_ONLY
     * 2.persist 更底层  更灵活  支持人为的修改缓存级别  persisit(StorageLevel.缓存级别)
     */
    word2OneRDD.cache()

    //word2OneRDD.persist(StorageLevel.MEMORY_AND_DISK_SER_2)

    word2OneRDD.collect().foreach(println)
    println("=======================")

    //word2OneRDD在缓存后,查看血缘关系
    println(word2OneRDD.toDebugString)

    word2OneRDD.collect().foreach(println)

    //如果word2OneRDD用完以后,可以释放缓存
    word2OneRDD.unpersist()


    //TODO 3.关闭连接
    sc.stop()

  }
}

代码说明:toDebugString查看血缘关系的
unpersist()使用说明:
下面有其他代码,如果有影响的话,最好把缓存释放掉。

运行结果:

(2) MapPartitionsRDD[3] at map at cache01.scala:23 []
 |  MapPartitionsRDD[2] at flatMap at cache01.scala:21 []
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt MapPartitionsRDD[1] at textFile at cache01.scala:19 []
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt HadoopRDD[0] at textFile at cache01.scala:19 []
******************************
******************************
******************************
******************************
******************************
******************************
(hello,1)
(spark,1)
(hello,1)
(spark,1)
(hello,1)
(scala,1)
=======================
(2) MapPartitionsRDD[3] at map at cache01.scala:23 [Memory Deserialized 1x Replicated]
 |       CachedPartitions: 2; MemorySize: 568.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 |  MapPartitionsRDD[2] at flatMap at cache01.scala:21 [Memory Deserialized 1x Replicated]
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt MapPartitionsRDD[1] at textFile at cache01.scala:19 [Memory Deserialized 1x Replicated]
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt HadoopRDD[0] at textFile at cache01.scala:19 [Memory Deserialized 1x Replicated]
(hello,1)
(spark,1)
(hello,1)
(spark,1)
(hello,1)
(scala,1)

结果说明:
可以看到cache和persist缓存比较灵活,可以找到血缘关系parent
2)源码解析

点击流程
第一步:点击cache()
在这里插入图片描述
第二步:点击persist
在这里插入图片描述
第三步:点击StorageLevel
在这里插入图片描述
结果:就是下面代码

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

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

得出点击源码结论

RDD持久化中:
1.cache底层是persist,cache不支持改缓存方式,persist支持改缓存级别,cache缓存默认选用MEMORY_ONLY
2.persist更底层、更灵活,支持人为修改缓存级别
修改方式:
例如
对应RDD.persist(StorageLevel.缓存级别)

注意:默认的存储级别都是仅在内存存储一份。在存储级别的末尾加上“_2”表示持久化的数据存为两份。SER:表示序列化。
在这里插入图片描述
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
3)自带缓存算子
Spark会自动对一些Shuffle操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点Shuffle失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist或cache。

object cache02 {

    def main(args: Array[String]): Unit = {

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3. 创建一个RDD,读取指定位置文件:hello atguigu atguigu
        val lineRdd: RDD[String] = sc.textFile("input1")

        //3.1.业务逻辑
        val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))

        val wordToOneRdd: RDD[(String, Int)] = wordRdd.map {
            word => {
                println("************")
                (word, 1)
            }
        }

        // 采用reduceByKey,自带缓存
        val wordByKeyRDD: RDD[(String, Int)] = wordToOneRdd.reduceByKey(_+_)

        //3.5 cache操作会增加血缘关系,不改变原有的血缘关系
        println(wordByKeyRDD.toDebugString)

        //3.4 数据缓存。
        //wordByKeyRDD.cache()

        //3.2 触发执行逻辑
        wordByKeyRDD.collect()

        println("-----------------")
        println(wordByKeyRDD.toDebugString)

        //3.3 再次触发执行逻辑
        wordByKeyRDD.collect()

        Thread.sleep(1000000)

        //4.关闭连接
        sc.stop()
    }
}

访问http://localhost:4040/jobs/页面,查看第一个和第二个job的DAG图。说明:增加缓存后血缘依赖关系仍然有,但是,第二个job取的数据是从缓存中取的。
在这里插入图片描述
在这里插入图片描述
自己在IDEA中测试:更上面代码case02类似
我使用正常的wordcount进行测试,想要实现,需要让程序睡一会,查看4040web端访问进行测试

package com.atguigu.cache

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

/**
 * @author dxy
 * @date 2021/2/24 10:19
 */
object cache02 {
  def main(args: Array[String]): Unit = {
    //TODO 1.创建SparkConf并设置App名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

    //TODO 2.创建SparkContext,该对象是提交Spark App的入口
    val sc: SparkContext = new SparkContext(conf)

    val lineRDD: RDD[String] = sc.textFile("D:\\DevelopmentTools\\spark\\SparkCoreTest1109\\input\\1.txt")

    val wordRDD: RDD[String] = lineRDD.flatMap(_.split(" "))

    val word2OneRDD: RDD[(String, Int)] = wordRDD.map(
      word => {
        println("******************************")
        (word, 1)
      }
    )

    val resultRDD: RDD[(String, Int)] = word2OneRDD.reduceByKey(_+_)

    resultRDD.collect().foreach(println)

    println("=======================================")

    resultRDD.collect().foreach(println)

    Thread.sleep(Long.MaxValue)


    //TODO 3.关闭连接
    sc.stop()

  }
}

代码说明:
reduceByKey()走shuffle,自带缓存
在这里插入图片描述
在这里插入图片描述
因此得出结论:
Shuffle自带缓存功能。

2.7.2RDD CheckPoint检查点

1)检查点:是通过将RDD中间结果写入磁盘。
2)为什么要做检查点?
由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
3)检查点存储路径:Checkpoint的数据通常是存储在HDFS等容错、高可用的文件系统
4)检查点数据存储格式为:二进制的文件
5)检查点切断血缘:在Checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除。
6)检查点触发时间:对RDD进行Checkpoint操作并不会马上被执行,必须执行Action操作才能触发。但是检查点为了数据安全,会从血缘关系的最开始执行一遍。
在这里插入图片描述
7)设置检查点步骤
(1)设置检查点数据存储路径:sc.setCheckpointDir("./checkpoint1")
(2)调用检查点方法:wordToOneRdd.checkpoint()

8)代码实现

package com.atguigu.cache

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

/**
 * @author dxy
 * @create 2021-02-24 9:45
 */
object checkpoint01 {
  def main(args: Array[String]): Unit = {
    //TODO 1 创建SparkConf配置文件,并设置App名称
    val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    //TODO 2 利用SparkConf创建sc对象
    val sc = new SparkContext(conf)

    //使用检查点之前,一定要先设置检查点存储路径
    sc.setCheckpointDir("D:\\IdeaProjects\\SparkCoreTest1109\\ck")

    val lineRDD: RDD[String] = sc.textFile("D:\\IdeaProjects\\SparkCoreTest1109\\input\\1.txt")

    val wordRDD: RDD[String] = lineRDD.flatMap(_.split(" "))

    val word2oneRDD: RDD[(String, Long)] = wordRDD.map(
      word => {
        (word, System.currentTimeMillis())
      }
    )


    //word2oneRDD在做检查点前 查看血缘关系
    println(word2oneRDD.toDebugString)

    //在检查点之前先做一下缓存
    word2oneRDD.cache()

    word2oneRDD.checkpoint()


    word2oneRDD.collect().foreach(println)



    println("============================")

    //word2oneRDD在做检查点后 查看血缘关系
    println(word2oneRDD.toDebugString)

    word2oneRDD.collect().foreach(println)

    println("============================")


    word2oneRDD.collect().foreach(println)


    Thread.sleep(Long.MaxValue)


    //TODO 3 关闭资源
    sc.stop()

  }

}

运行结果:

(2) MapPartitionsRDD[3] at map at checkpoint01.scala:26 []
 |  MapPartitionsRDD[2] at flatMap at checkpoint01.scala:24 []
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt MapPartitionsRDD[1] at textFile at checkpoint01.scala:22 []
 |  D:\DevelopmentTools\spark\SparkCoreTest1109\input\1.txt HadoopRDD[0] at textFile at checkpoint01.scala:22 []
(hello,1614216508492)
(spark,1614216508493)
(hello,1614216508493)
(spark,1614216508493)
(hello,1614216508492)
(scala,1614216508494)
========================
(2) MapPartitionsRDD[3] at map at checkpoint01.scala:26 [Memory Deserialized 1x Replicated]
 |       CachedPartitions: 2; MemorySize: 680.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
 |  ReliableCheckpointRDD[4] at collect at checkpoint01.scala:40 [Memory Deserialized 1x Replicated]
(hello,1614216508492)
(spark,1614216508493)
(hello,1614216508493)
(spark,1614216508493)
(hello,1614216508492)
(scala,1614216508494)
========================
(hello,1614216508492)
(spark,1614216508493)
(hello,1614216508493)
(spark,1614216508493)
(hello,1614216508492)
(scala,1614216508494)

在这里插入图片描述
读文件,没有设置分区默认为2,设置了分区与2取最小值

线程睡一会儿,打开4040端口
在这里插入图片描述
发现有4个job,3个行动算子=3个job,还有一个job在切断血缘关系之前,最后认一次parent,会从血缘关系开始执行一遍。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运行结果得出结论:

1.检查点会切断血缘关系,父RDD信息全部被删除;
2.只有遇到行动算子才会执行检查点操作;
3.检查点存储按照分区来存,存储文件格式是二进制
4.使用检查点时需要在使用之前一定要设置存储路径,不然运行报错。
5.检查点设置方式:
sc.setCheckpointDir(指定路径)
如果在本地存储,只需要指定本地路径即可,
如果指定存储在HDFS上,需要执行以下操作:
 // 1.设置访问HDFS集群的用户名
        System.setProperty("HADOOP_USER_NAME","atguigu")
 // 2.需要设置路径.需要提前在HDFS集群上创建/checkpoint路径
        sc.setCheckpointDir("hdfs://hadoop102:8020/checkpoint")

面试题:

1.在使用检查点时,对集群比较自信,不想在切断血缘关系之前再执行一遍job,怎么处理?

使用检查点之前先cache或者persist一下。控制台可以看到效果,但4040端口效果跟之前一下。
如果不设置的话,假如使用3次collect行动算子,第二次和第一次map的value结果不一致,第三次好第二次一致。
如果先缓存在检查点三次map的value是一致的。
value指获取系统当前时间。(如上面代码)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值