大数据开发Spark之RDD持久化(第三十二篇)

119 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

一、RDD持久化实战

scala代码
package com.strivelearn.scala
​
import org.apache.spark.{SparkConf, SparkContext}
​
/**
 * @author strivelearn
 * @version PersistRddScala.java, 2022年11月26日
 */
object PersistRddScala {
  def main(args: Array[String]): Unit = {
    //创建SparkContext
    val conf = new SparkConf()
    conf.setAppName("AccumulatorOpScala")
      .setMaster("local")
    val context = new SparkContext(conf)
    // cache默认是基于内存持久化的
    val dataRdd = context.textFile("/Users/strivelearn/Desktop/demo.txt").cache()
    var startTime = System.currentTimeMillis()
    var count = dataRdd.count()
    println(count)
    var endTime = System.currentTimeMillis()
    //324
    //第一次耗时:654
    println("第一次耗时:" + (endTime - startTime))
​
    startTime = System.currentTimeMillis()
    count = dataRdd.count()
    println(count)
    endTime = System.currentTimeMillis()
    //324
    //第二次耗时:30
    println("第二次耗时:" + (endTime - startTime))
​
  }
}
java
package com.strivelearn.java;
​
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.util.LongAccumulator;
​
import java.util.Arrays;
import java.util.List;
​
/**
 * @author strivelearn
 * @version PersistRddJava.java, 2022年11月26日
 */
public class PersistRddJava {
    public static void main(String[] args) {
        //1.创建sparkContext
        SparkConf sparkConf = new SparkConf();
        sparkConf.setAppName("PersistRddJava");
        sparkConf.setMaster("local");
        JavaSparkContext javaSparkContext = new JavaSparkContext(sparkConf);
​
        String path = "/Users/strivelearn/Desktop/demo.txt";
        JavaRDD<String> stringJavaRDD = javaSparkContext.textFile(path).cache();
        long startTime = System.currentTimeMillis();
        long count = stringJavaRDD.count();
        long endTime = System.currentTimeMillis();
        //第一次耗时:599
        System.out.println("第一次耗时:" + (endTime - startTime));
​
        startTime = System.currentTimeMillis();
        count = stringJavaRDD.count();
        endTime = System.currentTimeMillis();
        //耗时26
        System.out.println("第二次耗时:" + (endTime - startTime));
​
    }
}

二、共享变量的工作原理

  1. 默认情况下,一个算子函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中,此时每个task只能操作自己的那份数据变量的数据
  2. Spark提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)
  3. Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗
  4. 通过调用SparkContext的broadcast()方法,针对某个变量创建广播变量(广播变量是只读的)可以通过广播变量的value()方法获取值

Hadoop-Spark Broadcast Variable.drawio

2.1、Broadcast Variable
scala
package com.strivelearn.scala
​
import org.apache.spark.{SparkConf, SparkContext}
​
/**
 * @author strivelearn
 * @version BroadcastOpScala.java, 2022年11月26日
 */
object BroadcastOpScala {
  def main(args: Array[String]): Unit = {
    //创建SparkContext
    val conf = new SparkConf()
    conf.setAppName("BroadcastOpScala")
      .setMaster("local")
    val context = new SparkContext(conf)
​
    val dataRdd = context.parallelize(Array(1, 2, 3, 4, 5))
    var variable = 2
    //定义广播变量
    val variableBroadcast = context.broadcast(variable)
    //使用广播变量,调用其value
    dataRdd.map(_ * variableBroadcast.value).foreach(println)
    context.stop()
  }
}
java
package com.strivelearn.java;
​
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.broadcast.Broadcast;
​
import java.util.Arrays;
import java.util.List;
​
/**
 * @author strivelearn
 * @version BroadcastOpJava.java, 2022年11月26日
 */
public class BroadcastOpJava {
    public static void main(String[] args) {
        //1.创建sparkContext
        SparkConf sparkConf = new SparkConf();
        sparkConf.setAppName("BroadcastOpJava");
        sparkConf.setMaster("local");
        JavaSparkContext javaSparkContext = new JavaSparkContext(sparkConf);
​
        //创建集合
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        JavaRDD<Integer> rdd = javaSparkContext.parallelize(integers);
​
        Broadcast<Integer> broadcast = javaSparkContext.broadcast(2);
        rdd.map(num -> num * broadcast.value()).foreach(res -> System.out.println(res));
        javaSparkContext.stop();
    }
}
2.2、Accumulator
  1. 用于多个节点对一个变量进行共享性的操作
  2. Accumulator只提供了累加的功能,在task中只能对Accumulator进行累加操作,不能读取它的值,只有Driver进程中才可以读取Accumulator的值
scala代码
package com.strivelearn.scala
​
import org.apache.spark.{SparkConf, SparkContext}
​
/**
 * @author strivelearn
 * @version AccumulatorOpScala.java, 2022年11月26日
 */
object AccumulatorOpScala {
  def main(args: Array[String]): Unit = {
    //创建SparkContext
    val conf = new SparkConf()
    conf.setAppName("AccumulatorOpScala")
      .setMaster("local")
    val context = new SparkContext(conf)
    val dataRDD = context.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
​
    //这种写法是错误的,因为foreach代码是在worker节点上执行的
    //var total = 0和println是在Driver进程中执行
    //所以无法实现累加操作
    //并且foreach算子可能会在多个task中执行的,这样foreach内部实现的累加也不是最终全局累加的结果
    //    var total = 0
    //    dataRDD.foreach(num => total += num);
    //    println("total: " + total);
​
    //所以需要用到累加变量
    //1.定义累加变量
    val sumAccumulator = context.longAccumulator
    //2.使用累加变量
    dataRDD.foreach(num => sumAccumulator.add(num))
    println(sumAccumulator.value)
  }
}
java代码
package com.strivelearn.java;
​
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.util.LongAccumulator;
​
import java.util.Arrays;
import java.util.List;
​
/**
 * @author strivelearn
 * @version AccumulatorOpJava.java, 2022年11月26日
 */
public class AccumulatorOpJava {
    public static void main(String[] args) {
        //1.创建sparkContext
        SparkConf sparkConf = new SparkConf();
        sparkConf.setAppName("AccumulatorOpJava");
        sparkConf.setMaster("local");
        JavaSparkContext javaSparkContext = new JavaSparkContext(sparkConf);
​
        //创建集合
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        JavaRDD<Integer> rdd = javaSparkContext.parallelize(integers);
        LongAccumulator longAccumulator = javaSparkContext.sc().longAccumulator();
        rdd.foreach(integer -> longAccumulator.add(integer));
        System.out.println(longAccumulator.value());
    }
}

三、sortByKey如何实现全局排序

java代码要实现排序,需要把所有的数据都加载到内存中,这样的效果会比较低。(可能还会发生内存溢出的情况)。sortByKey如何解决这个问题呢?

通过RangePartitioner可以实现根据数据范围来分区:P1中的所有数据小于P2。P2中的所有的数据小于P3,依次类推。(采用分治思想)

Hadoop-sortByKey.drawio

3.1、实战

有一台服务器,32G内存的配置,如何在内存中对1T的数据进行排序?

思路:

  1. 先对原始数据进行抽样
  2. 然后进行切片,划分界限,把数据分为32份
  3. 每一份都是32G的数据(32*32=1024G)
  4. 这样针对每一份数据在内存中排序,排序完之后,把32份数据有序的合并