spark 缓存操作(cache checkpoint)与分区

spark cache:
1,cache 方法不是被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用
2, cache 是调用的 persist() 默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中
3,cache 默认的存储级别都是仅在内存存储一份,Spark的存储级别还有好多种,存储级别在object StorageLevel中定义的
4,缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
checkpoint 缓存:
1,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage(血统)做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销
2,为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移出。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发
cache 和 checkpoint 区别:
1,缓存把 RDD 计算出来然后放在内存中,但是RDD 的依赖链(相当于数据库中的redo 日志),当某个点某个 executor 宕了,上面cache 的RDD就会丢掉,需要通过依赖链重放计算出来,
2,checkpoint 是把 RDD 保存在 HDFS中, 是多副本可靠存储,不依靠RDD之间依赖链,是通过复制实现的高容错
package Day3
import org.apache.spark.{SparkConf, SparkContext}
object cache_point {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("praction").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(1 to 10)
    val nocache = rdd.map(_.toString+"["+System.currentTimeMillis+"]")
    val cache =  rdd.map(_.toString+"["+System.currentTimeMillis+"]")
    // 调用 cache 执行缓存, 值不变
    // jvm 开辟了内存
    //实际上是 使用 LRU cache 来缓存 RDD
    println(cache.cache)
    // 没有执行缓存 , 重新计算
    println(nocache.collect)

    //使用检查点机制
    //检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
    sc.setCheckpointDir("hdfs://hadoop:9000/checkpoint")
    val ch1 = sc.parallelize(1 to 2)
    val ch2 = ch1.map(_.toString+"["+System.currentTimeMillis+"]")
    val ch3 = ch1.map(_.toString+"["+System.currentTimeMillis+"]")
    ch3.checkpoint
    ch2.collect
    ch3.collect
  }}
    Spark目前支持Hash分区和Range分区,用户也可以自定义分区,Hash分区为当前的默认分区,Spark中分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数,只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None
分区方式
1,HashPartitioner分区的原理:对于给定的key,计算其hashCode,并除于分区的个数取余,如果余数小于0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID。
2,HashPartitioner分区弊端:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据。
3,RangePartitioner分区优势:尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大;但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内。
4,RangePartitioner作用:将一定范围内的数映射到某一个分区内,在实现中,分界的算法尤为重要。用到了水塘抽样算法
自定义分区:
package Day3
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
// 构造方法传递的是分区数
class custompation(numPar:Int) extends Partitioner{
  // 分区数
  override def numPartitions: Int = numPar
  // 获取分区键,生成分区号
  override def getPartition(key: Any): Int = {
    key.toString.hashCode%numPar
  }}
object App{
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[4]").setAppName("JDBC")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(Array(1,2,3,4,5))
    // 原始分区方式   mapPartitionsWithIndex 可以拿到分区的迭代器,又可以拿到分区编号
    rdd.mapPartitionsWithIndex((x,y)=>Iterator(x+":"+y.mkString("|"))).foreach(println)
    /*
    1:2
    2:3
    3:4|5
    0:1
     */
    // 查看自定义分区方式
    rdd.map((_,1)).partitionBy(new custompation(5))
      .mapPartitionsWithIndex((x,y)=>Iterator(x+":"+y.mkString("|"))).foreach(println)
    /*
    0:(2,1)
    1:(3,1)
    2:(4,1)
    4:(1,1)
    3:(5,1)
    * */
    sc.stop()}}

 

posted @ 2019-06-23 19:27  十七楼的羊  阅读(3165)  评论(0编辑  收藏  举报