spark core 持久化、自定义分区、数据读取与保存、共享变量

一、RDD持久化

RDD缓存

  • RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM的堆空间中。
  • 但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
  • 源码显示cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份,Spark的存储级别还有好多种,存储级别在object StorageLevel中定义的。

在这里插入图片描述
RDD CheckPoint

  • Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
  • 为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。
import org.apache.log4j.{
    
    Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{
    
    SparkConf, SparkContext}

/**
 * CREATE BY zhang
 * DATE 2020.09.21 14:10 星期一
 * DESC: spark 持久化
 */
object Demo01Cache {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)
    /* 初始化 SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))
    /* 并行化生成RDD */
    val scRDD: RDD[String] = sc.textFile("E:\\景航\\aaa.txt")

    /**
     * 多次被复用的RDD,可以用 cache
     * 也可以用 persist
     * 持久化是优化的一种重要手段
     */

    /* 用默认的存储级别 */
    val d: scRDD.type = scRDD.cache()
    /* 可以定义存储的级别 参数用于传递持久化存储级别 */
    val d1: scRDD.type = scRDD.persist()
    /*
      存储级别
        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)
     */

    /* 持久化 仅内存存储 */
    val d2: scRDD.type = scRDD.persist(StorageLevel.MEMORY_ONLY)
    
    /*
      设置ck的路径,生产环境 一般设置在分布式文件系统
        分布式文件系统 常有 hdfs  s3 touchyong fastdfs  GFS
     */
    sc.setCheckpointDir("E:\\景航\\aaa.txt")
    scRDD.checkpoint() //为了防止节点挂掉等 导致数据丢失,可以直接从检查点 恢复数据

    /* 关闭资源 */
    sc.stop()
  }
}

二、分区

  • Spark目前支持Hash分区和Range分区
  • 用户也可以自定义分区,Hash分区为当前的默认分区,
  • Spark中分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数?
  • 只有Key-Value类型的RDD才有分区器的,非Key-Value类型的RDD分区器的值是None
  • 每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。
import org.apache.log4j.{
    
    Level, Logger}
import org.apache.spark.{
    
    Partitioner, SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

object Demo02DefindPartition {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)
    /* 初始化 SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))
    /* 并行化生成RDD */
    val scRDD: RDD[String] = sc.textFile("E:\\aaa.txt")
    /* 扁平化操作 根据空格进行分割 */
    val flatMapRDD: RDD[String] = scRDD.flatMap(_.split(" "))
    /* 将value类型的RDD 转换成 key-value的RDD  (String, Int) */
    val pairRDD1: RDD[(String, Int)] = flatMapRDD.map((_, 1))
    /* 将value类型的RDD 转换成 key-value的RDD  (String, String) */
    val pairRDD2: RDD[(String, String)] = flatMapRDD.map(num => (num, num))

    /*
      自定义分区
        参数是 Partitioner
        需要自建一个类继承 Partitioner
     */
    val value: RDD[(String, Int)] = pairRDD1.partitionBy(new Mypartition(3))
    /* 关闭资源 */
    sc.stop()
  }

}

/* 自建一个类继承 Partitioner */
class Mypartition(num: Int) extends Partitioner{
    
    
  /* 指定分区个数 */
  override def numPartitions: Int = num
  /*
    设置分区规则
      这里按照单词的最后一个字母分区
   */
  override def getPartition(key: Any): Int = {
    
    
    /* 获取单词 */
    val word = key.toString
    /*
      获取单词的最后一个字符
      将这个字符根据ASCll表 转换成 Int类型
      对分区个数取余
     */
    word.substring(word.length - 1).toInt % numPartitions
  }
}

三、保存和读取 SequenceFile 文件

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

/**
 * CREATE BY zhang
 * DATE 2020.09.21 15:06 星期一
 * DESC: 文件类型读取和保存
 *  SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat File)*  Spark 有专门用来读取 SequenceFile 的接口。
 *  在 SparkContext 中,可以调用 sequenceFile[ keyClass, valueClass](path)*  注意:SequenceFile文件只针对PairRDD
 */
object Demo03SequenceFile {
    
    
  def main(args: Array[String]): Unit = {
    
    
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)
    /* 初始化 SparkContext */
    val sc = new SparkContext(new SparkConf().setAppName(this.getClass.getName).setMaster("local[2]"))
    /*
      保存为 SequenceFile 文件
     */

    /* 并行化生成RDD */
    val scRDD1: RDD[String] = sc.textFile("E:\\aaa.txt")
    /* 扁平化操作 返回 value 的RDD */
    val flatMapRDD: RDD[String] = scRDD1.flatMap(_.split(" "))
    /* 用map 将 value 的RDD 映射成 key-value 的RDD */
    val pairRDD: RDD[(String, Int)] = flatMapRDD.map((_, 1))
    /* 保存为SequenceFile */
    pairRDD.saveAsSequenceFile("E:/outputfile")


    /*
      读取 SequenceFile 文件
        并行化生成RDD
     */
    val scRDD2: RDD[(String, Int)] = sc.sequenceFile[String, Int]("E:/outputfile")
    /* 遍历 */
    scRDD2.foreach(println(_))
    /* 关闭资源 */
    sc.stop()
  }
}

四、共享变量

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

import org.apache.log4j.{
    
    Level, Logger}
import org.apache.spark.util.LongAccumulator
import org.apache.spark.{
    
    SparkConf, SparkContext}

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

    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化 SparkContext */
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
    val sc = new SparkContext(conf)

    //driver 端的变量 声明一个累加器
    val accumulator: LongAccumulator = sc.longAccumulator
    println("accumulator:"+accumulator.value)

    //    sc.accumulator(0)
    /* 并行化生成RDD,设置分区1个 */
    val numRdd = sc.parallelize(Array(1,2,3,4,5),1)

    numRdd.map(i => {
    
    
      /* excutor端改变累加器的值 */
      accumulator.add(i)
      println("excutor端的accumulator:"+accumulator.value)
    }).collect()

    println("-----------")
    println("driver端的accumulator:"+accumulator.value)

    /* 关闭资源 */
    sc.stop()
  }
}

自定义累加器

import java.util
import org.apache.log4j.{
    
    Level, Logger}
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{
    
    SparkConf, SparkContext}

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

    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化 SparkContext */
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
    val sc = new SparkContext(conf)

    //自定义的accumulator
    val accum = new DefindAccumulator
    //Accumulator must be registered before send to executor
    sc.register(accum)

    //过滤掉带字母的,然后求和
    val sumRdd = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7cd", "8", "9"), 2)

    val sum = sumRdd.filter(line =>{
    
    
      val pattern = """^-?(\d+)"""
      val flag = line.matches(pattern)
      //纯数字 累加求和,非纯数字,累加到累加器
      if (!flag) {
    
    
        accum.add(line)
      }
      flag
    }).map(_.toInt).reduce(_+_)


    println("sum: " + sum) //纯数字 累加求和

    //非纯数字  累加到累加器  最后结果做遍历
    val value: util.HashSet[String] = accum.value

    println(value)

    /* 关闭资源 */
    sc.stop()
  }

}

//AccumulatorV2[String,Set[String]]  输入的是每个单词,输出的是一个集合
class DefindAccumulator extends AccumulatorV2[String,util.HashSet[String]]{
    
    

  private val hashSet = new util.HashSet[String]()

  //判断是否为空
  override def isZero: Boolean = hashSet.isEmpty

  //重置
  override def reset(): Unit = hashSet.clear()

  //添加元素
  override def add(v: String): Unit = {
    
    
    hashSet.add(v)
  }

  //多个分区的合并
  override def merge(other: AccumulatorV2[String, util.HashSet[String]]): Unit = {
    
    

    other match {
    
    
      //每个分区计算的结果(accu.value)都是 HashSet,现在要多个分区的结果合并
      case accu:AccumulatorV2[String, util.HashSet[String]] =>{
    
    
        hashSet.addAll(accu.value)  //多个hashSet合并
      }
    }
  }

  //将当前的结果 复制到新的的一个累加器里面
  override def copy(): AccumulatorV2[String, util.HashSet[String]] = {
    
    
    val accumulator = new DefindAccumulator
    //复制
    accumulator.hashSet.addAll(hashSet)
    accumulator  //返回对象
  }

  //返回值
  override def value: util.HashSet[String] = hashSet
}

广播变量(调优策略)

  • 广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark会为每个任务分别发送。
  • 使用广播变量的过程如下:
    (1) 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现。
    (2) 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
    (3) 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。
import org.apache.log4j.{
    
    Level, Logger}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.{
    
    SparkConf, SparkContext}

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

	/* 常用作广播大变量 */
    //控制日志输出
    Logger.getLogger("org").setLevel(Level.ERROR)

    /* 初始化 SparkContext */
    val conf = new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[2]")
    val sc = new SparkContext(conf)

    val num = 100
    val numRdd = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),4)
    // 不广播时 会将变量 在每一个task 都要copy 一份
    numRdd.map(_+num)
    
    // 先将变量广播出去
    val broadcastvalue: Broadcast[Int] = sc.broadcast(num)

    numRdd.map(i => {
    
    
      //广播变量的取值
      var value = broadcastvalue.value  
      value += i
      println(value)
    }).collect()

    /* 关闭资源 */
    sc.stop()
  }
}

猜你喜欢

转载自blog.csdn.net/weixin_46122692/article/details/109036717