• Spark RDD持久化原理+共享变量原理(Broadcast Variable和Accumulator)


    2.4 RDD 持久化

    2.4.1 RDD 的缓存

     Spark 速度非常快的原因之一,就是在不同操作中可以在内存中持久化或缓存个数据集。当持久化某个 RDD 后,每一个节点都将把计算的分片结果保存在内存中,并在对此 RDD 或衍生出的 RDD 进行的其他动作中重用。这使得后续的动作变得更加迅速。RDD 相关的持久化和缓存,是 Spark 最重要的特征之一。可以说,缓存是 Spark 构建迭代式算法快速交互式查询的关键。如果一个有持久化数据的节点发生故障,Spark 会在需要用到缓存的数据时重算丢失的数据分区。如果希望节点故障的情况不会拖累我们的执行速度,也可以把数据备份到多个节点上。 
      持久化也是懒执行的,持久化有两个操作:persist(StorageLevel),另外一个是 cache, cache 就相当于 MEMORY_ONLY 的 persist。

    2.4.2 RDD 缓存方式

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

      在存储级别的末尾加上“_2”来把持久化数据存为两份。


      缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除,RDD 的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。
      注意:使用 Tachyon 可以实现堆外缓存。
      
    RDD持久化原理

    Spark非常重要的一个功能特性就是可以将RDD持久化在内存中。当对RDD执行持久化操作时,每个节点都会将自己操作的RDD的partition持久化到内存中,并且在之后对该RDD的反复使用中,直接使用内存缓存的partition。这样的话,对于针对一个RDD反复执行多个操作的场景,就只要对RDD计算一次即可,后面直接使用该RDD,而不需要反复计算多次该RDD。

    巧妙使用RDD持久化,甚至在某些场景下,可以将spark应用程序的性能提升10倍。对于迭代式算法和快速交互式应用来说,RDD持久化,是非常重要的。

    要持久化一个RDD,只要调用其cache()或者persist()方法即可。在该RDD第一次被计算出来时,就会直接缓存在每个节点中。而且Spark的持久化机制还是自动容错的,如果持久化的RDD的任何partition丢失了,那么Spark会自动通过其源RDD,使用transformation操作重新计算该partition。

    cache()和persist()的区别在于,cache()是persist()的一种简化方式,cache()的底层就是调用的persist()的无参版本,同时就是调用persist(MEMORY_ONLY),将数据持久化到内存中。如果需要从内存中清楚缓存,那么可以使用unpersist()方法。

    Spark自己也会在shuffle操作时,进行数据的持久化,比如写入磁盘,主要是为了在节点失败时,避免需要重新计算整个过程。

    不使用持久化

    使用持久化

     

    RDD持久化策略

    RDD持久化是可以手动选择不同的策略的。比如可以将RDD持久化在内存中、持久化到磁盘上、使用序列化的方式持久化,多持久化的数据进行多路复

    如何选择RDD持久化策略?

    Spark提供的多种持久化级别,主要是为了在CPU和内存消耗之间进行取舍。下面是一些通用的持久化级别的选择建议:

    1、优先使用MEMORY_ONLY,如果可以缓存所有数据的话,那么就使用这种策略。因为纯内存速度最快,而且没有序列化,不需要消耗CPU进行反序列化操作。
    2、如果MEMORY_ONLY策略,无法存储的下所有数据的话,那么使用MEMORY_ONLY_SER,将数据进行序列化进行存储,纯内存操作还是非常快,只是要消耗CPU进行反序列化。
    3、如果需要进行快速的失败恢复,那么就选择带后缀为_2的策略,进行数据的备份,这样在失败时,就不需要重新计算了。
    4、能不使用DISK相关的策略,就不用使用,有的时候,从磁盘读取数据,还不如重新计算一次。

    共享变量工作原理

    Spark一个非常重要的特性就是共享变量。

    默认情况下,如果在一个算子的函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。如果多个task想要共享某个变量,那么这种方式是做不到的。

    Spark为此提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。

    Broadcast Variable

    Spark提供的Broadcast Variable,是只读的。并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本。因此其最大作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark自己内部也使用了高效的广播算法来减少网络消耗。

    可以通过调用SparkContext的broadcast()方法,来针对某个变量创建广播变量。然后在算子的函数内,使用到广播变量时,每个节点只会拷贝一份副本了。每个节点可以使用广播变量的value()方法获取值。记住,广播变量,是只读的。


    val factor = 3
    val factorBroadcast = sc.broadcast(factor)

    val arr = Array(1, 2, 3, 4, 5)
    val rdd = sc.parallelize(arr)
    val multipleRdd = rdd.map(num => num * factorBroadcast.value())

    multipleRdd.foreach(num => println(num))


       /**

     * 广播变量
    * @author Administrator
    *
    */
    public class BroadcastVariable {
    public static void main(String[] args) {
    SparkConf conf = new SparkConf()
    .setAppName("BroadcastVariable")
    .setMaster("local");
    JavaSparkContext sc = new JavaSparkContext(conf);

    // 在java中,创建共享变量,就是调用SparkContext的broadcast()方法
    // 获取的返回结果是Broadcast<T>类型
    final int factor=3;
    final Broadcast<Integer> factorBroadcast=sc.broadcast(factor);
    List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5);
    JavaRDD<Integer> numbers = sc.parallelize(numberList);
    // 让集合中的每个数字,都乘以外部定义的那个factor
    JavaRDD<Integer> multipleNumbers=numbers.map(new Function<Integer, Integer>() {
    @Override
    public Integer call(Integer integer) throws Exception {
    int factor=factorBroadcast.value();
    return integer*factor;
    }
    });
    multipleNumbers.foreach(new VoidFunction<Integer>() {
    @Override
    public void call(Integer integer) throws Exception {
    System.out.println(integer);
    }
    });
    sc.close();
    }
    }


    Accumulator
    Spark提供的Accumulator,主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是确给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。


    val sumAccumulator = sc.accumulator(0)

    val arr = Array(1, 2, 3, 4, 5)
    val rdd = sc.parallelize(arr)
    rdd.foreach(num => sumAccumulator += num)

    println(sumAccumulator.value)


    /**
    * 累加变量
    * @author Administrator
    *
    */
    public class AccumulatorVariable {
    public static void main(String[] args) {
    SparkConf conf = new SparkConf()
    .setAppName("Accumulator")
    .setMaster("local");
    JavaSparkContext sc = new JavaSparkContext(conf);

    // 创建Accumulator变量
    // 需要调用SparkContext的accumulator()方法
    final Accumulator sum=sc.accumulator(0);

    List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5);
    JavaRDD<Integer> numbers = sc.parallelize(numberList);
    numbers.foreach(new VoidFunction<Integer>() {
    @Override
    public void call(Integer integer) throws Exception {
    // 然后在函数内部,就可以对Accumulator变量,调用add()方法,累加值
    sum.add(integer);
    }
    });
    // 在driver程序中,可以调用Accumulator的value()方法,获取其值
    System.out.println(sum.value());
    sc.close();
    }

    }

     

  • 相关阅读:
    读《持续交付2.0》
    “兼职”运维的常用命令
    技术管理者怎样跳出“泥潭”
    使用RabbitMQ实现接口补偿
    dotNET Core 中怎样操作 AD?
    dotNET Core实现分布式环境下的流水号唯一
    Git 远程仓库
    分之管理
    git 基本操作----git diff
    git 基本操作----git reset、log
  • 原文地址:https://www.cnblogs.com/Transkai/p/11347224.html
Copyright © 2020-2023  润新知