大数据开发学习2.1-Spark Core

158 阅读16分钟

RDD概述

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象。

代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。

RDD五大特性:

  • Partition,数据集的基本组成单位
  • 计算每个分区的函数
  • RDD之间的依赖关系
  • Partitioner,RDD的分片函数
  • 列表,存储存取每个Partition的优先位置

RDD编程

RDD的创建

在Spark中创建RDD的创建方式可以分为三种:

  • 从集合中创建RDD
  • 从外部存储创建RDD
  • 从其他RDD创建

环境准备

新建maven工程

在pom文件中添加spark-core的依赖

<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.12</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

从集合中创建

分为4大步骤:

  1. 创建配置对象
  2. 创建sparkContext
  3. 构建RDD及处理逻辑
  4. 关闭sparkContext
public class Test01_List {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<String> stringRDD = sc.parallelize(Arrays.asList("hello", "spark"));
​
        List<String> result = stringRDD.collect();
​
        for (String s : result) {
            System.out.println(s);
        }
​
        // 4. 关闭sc
        sc.stop();
    }
}

从外部存储系统的数据集创建

public class Test02_File {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<String> lineRDD = sc.textFile("input");
​
        List<String> result = lineRDD.collect();
​
        for (String s : result) {
            System.out.println(s);
        }
​
        // 4. 关闭sc
        sc.stop();
    }
}

从其他RDD创建

详见之后算子章节

Transformation转换算子

Value类型

map()映射

当某个RDD执行map方法时,会遍历该RDD中的每一个数据项,并依次应用f函数,从而产生一个新的RDD。

map()方法中需要传入一个匿名函数,其中匿名函数需要设置两个泛型

第1个泛型:调用map()方法的RDD中的数据类型

第2个泛型:调用map()方法后RDD中的数据类型,即映射后的数据类型

public class Test01_Map {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<String> lineRDD = sc.textFile("input/1.txt");
​
        // 需求:每行结尾拼接||
        // 两种写法  lambda表达式写法(匿名函数) 
        JavaRDD<String> mapRDD = lineRDD.map(s -> s + "||");
​
        // 匿名函数写法 
        JavaRDD<String> mapRDD1 = lineRDD.map(new Function<String, String>() {
            @Override
            public String call(String v1) throws Exception {
                return v1 + "||";
            }
        });
​
        for (String s : mapRDD.collect()) {
            System.out.println(s);
        }
​
        // 输出数据的函数写法
        mapRDD1.collect().forEach(a -> System.out.println(a));
        mapRDD1.collect().forEach(System.out::println);
​
​
        // 4. 关闭sc
        sc.stop();
    }
}

flatMap()扁平化

与map操作类似,将RDD中的每一个元素通过应用f函数依次转换为新的元素,并封装到RDD中。

区别:在flatMap操作中,f函数的返回值是一个集合,并且会将每一个该集合中的元素拆分出来放到新的RDD中。

flatmap()需要传入和map()方法类似的一个匿名函数,也需要设置两个泛型,与map相同

注意方法返回值是一个迭代器

public class Test02_FlatMap {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        ArrayList<List<String>> arrayLists = new ArrayList<>();
​
        arrayLists.add(Arrays.asList("1","2","3"));
        arrayLists.add(Arrays.asList("4","5","6"));
​
​
        JavaRDD<List<String>> listJavaRDD = sc.parallelize(arrayLists,2);
        // 对于集合嵌套的RDD 可以将元素打散
        // 泛型为打散之后的元素类型
        JavaRDD<String> stringJavaRDD = listJavaRDD.flatMap(new FlatMapFunction<List<String>, String>() {
            @Override
            public Iterator<String> call(List<String> strings) throws Exception {
​
                return strings.iterator();
            }
        });
​
        stringJavaRDD. collect().forEach(System.out::println);
​
        // 通常情况下需要自己将元素转换为集合
        JavaRDD<String> lineRDD = sc.textFile("input/2.txt");
​
        JavaRDD<String> stringJavaRDD1 = lineRDD.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public Iterator<String> call(String s) throws Exception {
                String[] s1 = s.split(" ");
                return Arrays.asList(s1).iterator();
            }
        });
​
        stringJavaRDD1. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

groupBy()分组

分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。

分组规则需要自行传入匿名函数进行设置

public class Test03_GroupBy {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        // 泛型为分组标记的类型
        JavaPairRDD<Integer, Iterable<Integer>> groupByRDD = integerJavaRDD.groupBy(new Function<Integer, Integer>() {
            @Override
            public Integer call(Integer v1) throws Exception {
                return v1 % 2;
            }
        });
​
​
        groupByRDD.collect().forEach(System.out::println);
​
        // 类型可以任意修改
        JavaPairRDD<Boolean, Iterable<Integer>> groupByRDD1 = integerJavaRDD.groupBy(new Function<Integer, Boolean>() {
            @Override
            public Boolean call(Integer v1) throws Exception {
                return v1 % 2 == 0;
            }
        });
​
        groupByRDD1. collect().forEach(System.out::println);
​
    Thread.sleep(600000);
        // 4. 关闭sc
        sc.stop();
​
    }
}

groupBy会存在shuffle过程

shuffle:将不同的分区数据进行打乱重组的过程

shuffle一定会落盘。可以在local模式下执行程序,通过4040看效果。

filter()过滤

接收一个返回值为布尔类型的函数作为参数。当某个RDD调用filter方法时,会对该RDD中每一个元素应用f函数,如果返回值类型为true,则该元素会被添加到新的RDD中。

public class Test04_Filter {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        JavaRDD<Integer> filterRDD = integerJavaRDD.filter(new Function<Integer, Boolean>() {
            @Override
            public Boolean call(Integer v1) throws Exception {
                return v1 % 2 == 0;
            }
        });
​
        filterRDD. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

distinct()去重

对内部的元素去重,并将去重后的元素放到新的RDD中

public class Test05_Distinct {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6), 2);
​
        // 底层使用分布式分组去重  所有速度比较慢,但是不会OOM
        JavaRDD<Integer> distinct = integerJavaRDD.distinct();
​
        distinct. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

注意:distinct会存在shuffle过程

sortBy()排序

该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。排序后新产生的RDD的分区数与原RDD的分区数一致。Spark的排序结果是全局有序。

public class Test6_SortBy {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(5, 8, 1, 11, 20), 2);
​
        // (1)泛型为以谁作为标准排序  (2) true为正序  (3) 排序之后的分区个数
        JavaRDD<Integer> sortByRDD = integerJavaRDD.sortBy(new Function<Integer, Integer>() {
            @Override
            public Integer call(Integer v1) throws Exception {
                return v1;
            }
        }, true, 2);
​
        sortByRDD. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

Key-Value类型

要想使用Key-Value类型的算子首先需要使用特定的方法转换为PairRDD

public class Test01_pairRDD{
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
​
        JavaPairRDD<Integer, Integer> pairRDD = integerJavaRDD.mapToPair(new PairFunction<Integer, Integer, Integer>() {
            @Override
            public Tuple2<Integer, Integer> call(Integer integer) throws Exception {
                return new Tuple2<>(integer, integer);
            }
        });
​
        pairRDD. collect().forEach(System.out::println);
​
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

mapValues()

只对V进行操作

public class Test02_MapValues {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaPairRDD<String, String> javaPairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>("k", "v"), new Tuple2<>("k1", "v1"), new Tuple2<>("k2", "v2")));
​
        // 只修改value 不修改key
        JavaPairRDD<String, String> mapValuesRDD = javaPairRDD.mapValues(new Function<String, String>() {
            @Override
            public String call(String v1) throws Exception {
                return v1 + "|||";
            }
        });
​
        mapValuesRDD. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

groupByKey()

按照K重新分组

需求:统计单词个数 WordCount

public class Test03_GroupByKey {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<String> integerJavaRDD = sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);
​
        // 统计单词出现次数
        JavaPairRDD<String, Integer> pairRDD = integerJavaRDD.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });
​
        // 聚合相同的key
        JavaPairRDD<String, Iterable<Integer>> groupByKeyRDD = pairRDD.groupByKey();
​
        // 合并值
        JavaPairRDD<String, Integer> result = groupByKeyRDD.mapValues(new Function<Iterable<Integer>, Integer>() {
            @Override
            public Integer call(Iterable<Integer> v1) throws Exception {
                Integer sum = 0;
                for (Integer integer : v1) {
                    sum += integer;
                }
                return sum;
            }
        });
​
        result. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}}

reduceByKey()

该操作可以将RDD[K,V]中的元素按照相同的K对V进行聚合。其存在多种重载形式,还可以设置新RDD的分区数。

需求:统计单词个数 WordCount

public class Test04_ReduceByKey {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<String> integerJavaRDD = sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);
​
        // 统计单词出现次数
        JavaPairRDD<String, Integer> pairRDD = integerJavaRDD.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });
​
        // 聚合相同的key
        JavaPairRDD<String, Integer> result = pairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });
​
        result. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

reduceByKey和groupByKey区别

1)reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[K,V]。

2)groupByKey:按照key进行分组,直接进行shuffle。

建议:在不影响业务逻辑的前提下,优先选用reduceByKey

sortByKey()

按照K进行排序

public class Test05_SortByKey {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaPairRDD<Integer, String> javaPairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>(4, "a"), new Tuple2<>(3, "c"), new Tuple2<>(2, "d")));
​
        // 填写布尔类型选择正序倒序
        JavaPairRDD<Integer, String> pairRDD = javaPairRDD.sortByKey(false);
​
        pairRDD. collect().forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

Action行动算子

行动算子是触发了整个作业的执行。因为转换算子都是懒加载,并不会立即执行。

collect()

以数组的形式返回数据集

public class Test01_Collect {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        List<Integer> collect = integerJavaRDD.collect();
​
        for (Integer integer : collect) {
            System.out.println(integer);
        }
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

count()

返回RDD中元素个数

public class Test02_Count {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        long count = integerJavaRDD.count();
​
        System.out.println(count);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

first()

返回RDD中的第一个元素

public class Test03_First {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        Integer first = integerJavaRDD.first();
​
        System.out.println(first);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

take()

返回由RDD前n个元素组成的数组

public class Test04_Take {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        List<Integer> list = integerJavaRDD.take(3);
​
        list.forEach(System.out::println);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

countByKey()

统计每种key的个数

public class Test05_CountByKey {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaPairRDD<String, Integer> pairRDD = sc.parallelizePairs(Arrays.asList(new Tuple2<>("a", 8), new Tuple2<>("b", 8), new Tuple2<>("a", 8), new Tuple2<>("d", 8)));
​
        Map<String, Long> map = pairRDD.countByKey();
​
        System.out.println(map);
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

save相关算子

saveAsTextFile(path) 保存成Text文件

saveAsObjectFile(path) 序列化成对象保存到文件

public class Test06_Save {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
​
        integerJavaRDD.saveAsTextFile("output");
​
        integerJavaRDD.saveAsObjectFile("output1");
​
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

foreach()

遍历RDD中每一个元素

public class Test07_Foreach {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),4);
​
        integerJavaRDD.foreach(new VoidFunction<Integer>() {
            @Override
            public void call(Integer integer) throws Exception {
                System.out.println(integer);
            }
        });
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

foreachPartition()

遍历RDD中每一个分区

public class Test08_ForeachPartition {
    public static void main(String[] args) {
        // 1. 创建配置对象
        SparkConf conf = new SparkConf().setAppName("core").setMaster("local[*]");
​
        // 2. 创建sc环境
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> parallelize = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6), 2);
​
        // 多线程一起计算   分区间无序  单个分区有序
        parallelize.foreachPartition(new VoidFunction<Iterator<Integer>>() {
            @Override
            public void call(Iterator<Integer> integerIterator) throws Exception {
                // 一次处理一个分区的数据
                while (integerIterator.hasNext()) {
                    Integer next = integerIterator.next();
                    System.out.println(next);
                }
            }
        });
​
​
        // 4. 关闭sc
        sc.stop();
    }
}

RDD序列化

Serializable序列化

在实际开发中我们往往需要自己定义一些对于RDD的操作,那么此时需要注意的是,初始化工作是在Driver端进行的,而实际运行程序是在Executor端进行的,这就涉及到了跨进程通信,是需要序列化的。

设计一个javaBean User

@Data
public class User implements Serializable {
    private String name;
    private Integer age;
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}
public class Test01_Ser {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        User zhangsan = new User("zhangsan", 13);
        User lisi = new User("lisi", 13);
​
        JavaRDD<User> userJavaRDD = sc.parallelize(Arrays.asList(zhangsan, lisi), 2);
​
        JavaRDD<User> mapRDD = userJavaRDD.map(new Function<User, User>() {
            @Override
            public User call(User v1) throws Exception {
                return new User(v1.getName(), v1.getAge() + 1);
            }
        });
​
        mapRDD. collect().forEach(System.out::println);
​
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

在这种方式下,只有User实现了序列化接口才能在RDD中进行运算

Kryo序列化

Java的序列化能够序列化任何的类。但是比较重,序列化后对象的体积也比较大。

Spark出于性能的考虑,Spark2.0开始支持另外一种Kryo序列化机制。Kryo速度是Serializable的10倍。当RDD在Shuffle数据的时候,简单数据类型、数组和字符串类型已经在Spark内部使用Kryo来序列化。

public class Test02_Kryo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore")
                // 替换默认的序列化机制
                .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
                // 注册需要使用kryo序列化的自定义类
                .registerKryoClasses(new Class[]{Class.forName("com.myspark.bean.User")});
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        User zhangsan = new User("zhangsan", 13);
        User lisi = new User("lisi", 13);
​
        JavaRDD<User> userJavaRDD = sc.parallelize(Arrays.asList(zhangsan, lisi), 2);
​
        JavaRDD<User> mapRDD = userJavaRDD.map(new Function<User, User>() {
            @Override
            public User call(User v1) throws Exception {
                return new User(v1.getName(), v1.getAge() + 1);
            }
        });
​
        mapRDD. collect().forEach(System.out::println);
​
​
        // 4. 关闭sc
        sc.stop();
​
    }
}

RDD依赖关系

RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

//调用方式
rdd.DebugString()

窄依赖

窄依赖表示每一个父RDD的Partition最多被子RDD的一个Partition使用(一对一or多对一)

宽依赖

宽依赖表示同一个父RDD的Partition被多个子RDD的Partition依赖(只能是一对多) ,会引起Shuffle

具有宽依赖的transformations包括:sort、reduceByKey、groupByKey、join和调用rePartition函数的任何操作。 宽依赖对Spark去评估一个transformations有更加重要的影响,比如对性能的影响。 在不影响业务要求的情况下,要尽量避免使用有宽依赖的转换算子,因为有宽依赖,就一定会走shuffle,影响性能。

Stage任务划分

DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。例如,DAG记录了RDD的转换过程和任务的阶段。

image-20221019204843160.png

RDD任务切分中间分为:Application、Job、Stage和Task

(1)Application:初始化一个SparkContext即生成一个Application;

(2)Job:一个Action算子就会生成一个Job;

(3)Stage:Stage等于宽依赖的个数加1;

(4)Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。

RDD持久化

RDD Cache缓存

RDD通过Cache或者Persist方法将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在JVM的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的action算子时,该RDD将会被缓存在计算节点的内存中,并供后面重用。

public class Test01_Cache {
    public static void main(String[] args){
        //TODO 1 创建sparkConf配置文件
        SparkConf conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]");
​
        //TODO 2 创建sparkContext上下文计算对象
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        //1.读取数据
        JavaRDD<String> lineRDD = sc.textFile("D:\IdeaProjects\spark3303\input\1.txt");
        //2.按照空格切分数据
        JavaRDD<String> wordRDD = lineRDD.flatMap(line -> Arrays.asList(line.split(" ")).iterator());
        //3.转换数据结构 word => (word,1)
        JavaPairRDD<String, Integer> word2oneRDD = wordRDD.mapToPair(word ->{
            System.out.println("*******************");
            return new Tuple2<>(word, 1);
        });
​
        //缓存前打印血缘关系
        System.out.println(word2oneRDD.toDebugString());
        //添加缓存方法有两个 1.cache  2.persist(底层方法,可以修改缓存级别)
        word2oneRDD.cache();
        //word2oneRDD.persist(StorageLevel.MEMORY_AND_DISK());
​
        //任务1
        word2oneRDD. collect().forEach(System.out::println);
​
        //缓存后打印血缘关系
        System.out.println(word2oneRDD.toDebugString());
​
        //任务2
        word2oneRDD. collect().forEach(System.out::println);
​
        //缓存使用完,记得释放缓存
        word2oneRDD.unpersist();
        
        //TODO 3 关闭资源
        sc.stop();
    }
}

源码:

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)

注意:默认的存储级别都是仅在内存存储一份。在存储级别的末尾加上“_2”表示持久化的数据存为两份。SER:表示序列化。

image-20221019205426759.png

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

自带缓存算子

park会自动对一些Shuffle操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点Shuffle失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist或cache。

RDD CheckPoint检查点

(1)检查点:是通过将RDD中间结果写入磁盘。

(2)为什么要做检查点? 由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。

(3)检查点存储路径:Checkpoint的数据通常是存储在HDFS等容错、高可用的文件系统

(4)检查点数据存储格式为:二进制的文件

(5)检查点切断血缘:在Checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除

(6)检查点触发时间:对RDD进行Checkpoint操作并不会马上被执行,必须执行Action操作才能触发。但是检查点为了数据安全,会从血缘关系的最开始执行一遍

代码实现:

public class Test02_CheckPoint {
    public static void main(String[] args) throws InterruptedException {
        //TODO 1 创建sparkConf配置文件
        SparkConf conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]");
​
        //TODO 2 创建sparkContext上下文计算对象
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        //设置检查点一定要设置检查点目录
        sc.setCheckpointDir("D:\IdeaProjects\spark3303\ck");
​
        //1.读取数据
        JavaRDD<String> lineRDD = sc.textFile("D:\IdeaProjects\spark3303\input\1.txt");
        //2.按照空格切分数据
        JavaRDD<String> wordRDD = lineRDD.flatMap(line -> Arrays.asList(line.split(" ")).iterator());
        //3.转换数据结构 word => (word,1)
        JavaPairRDD<String, Long> word2oneRDD = wordRDD.mapToPair(word ->{
            return new Tuple2<>(word, System.currentTimeMillis());
        });
​
        //检查点前打印血缘关系
        System.out.println(word2oneRDD.toDebugString());
​
        //检查点前先缓存,避免从头计算
        word2oneRDD.cache();
        //添加检查点
        word2oneRDD.checkpoint();
​
        //任务1
        word2oneRDD. collect().forEach(System.out::println);
​
        //检查点后打印血缘关系
        System.out.println(word2oneRDD.toDebugString());
​
        //任务2
        word2oneRDD. collect().forEach(System.out::println);
        //任务3
        word2oneRDD. collect().forEach(System.out::println);
​
        Thread.sleep(Long.MAX_VALUE);
        
        //TODO 3 关闭资源
        sc.stop();
    }
}

缓存和检查点区别

(1)Cache缓存只是将数据保存起来,不切断血缘依赖。Checkpoint检查点切断血缘依赖。

(2)Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。

(3)建议对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,否则需要再从头计算一次RDD。

(4)如果使用完了缓存,可以通过unpersist()方法释放缓存。

累加器

累加器:分布式共享只写变量。(Executor和Executor之间不能读数据)

累加器用来把Executor端变量信息聚合到Driver端。在Driver中定义的一个变量,在Executor端的每个task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行合并计算。

public class Test01_Acc {
    public static void main(String[] args) {
        //TODO 1 创建sparkConf配置文件
        SparkConf conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]");
​
        //TODO 2 创建sparkContext上下文计算对象
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        JavaPairRDD<String, Integer> tupleRDD = sc.parallelizePairs(Arrays.asList(
                new Tuple2<>("a", 1), 
                new Tuple2<>("a", 2), 
                new Tuple2<>("a", 3), 
                new Tuple2<>("a", 4)),2);
        //需求:求出a的总个数
        //实现1 算子实现
       /* JavaPairRDD<String, Integer> resultRDD = tupleRDD.reduceByKey((v1, v2) -> v1 + v2);
        resultRDD. collect().forEach(System.out::println);
        */
​
       //实现2 普通变量无法实现
        /* final Integer[] sum = {0};
        tupleRDD.foreach(new VoidFunction<Tuple2<String, Integer>>() {
            @Override
            public void call(Tuple2<String, Integer> tuple2) throws Exception {
                sum[0] += tuple2._2;
                System.out.println("sum: " + sum[0]);
            }
        });
​
        System.out.println(new Tuple2<String, Integer>("a", sum[0]));*/
​
        //实现3 累加器实现
        //1 创建累加器
        //转变为scala的sc,才可以创建累加器 java的sc没有
        LongAccumulator myAcc = JavaSparkContext.toSparkContext(sc).longAccumulator("myAcc");
        //2 使用累加器
        tupleRDD.foreach(new VoidFunction<Tuple2<String, Integer>>() {
            @Override
            public void call(Tuple2<String, Integer> tuple2) throws Exception {
                myAcc.add(tuple2._2);
                //4 分布式共享只写变量  不要在executor端读取累加器的值  因为读的不准确
                System.out.println("sum: "+myAcc.value());
            }
        });
​
        //3 获取累加器的值
        System.out.println(new Tuple2<String, Long>("a", myAcc.value()));
        
        //TODO 3 关闭资源
        sc.stop();
    }
}

注意:Executor端的任务不能读取累加器的值(例如:在Executor端调用sum.value,获取的值不是累加器最终的值)。因此我们说,累加器是一个分布式共享只写变量。

累加器要放在行动算子中

因为转换算子执行的次数取决于job的数量,如果一个spark应用有多个行动算子,那么转换算子中的累加器可能会发生不止一次更新,导致结果错误。所以,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在foreach()这样的行动算子中。

广播变量

广播变量:分布式共享只读变量

广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark Task操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来会很顺手。在多个Task并行操作中使用同一个变量,但是Spark会为每个Task任务分别发送。

public class Test02_Broadcast {
    public static void main(String[] args) {
        // 1.创建配置对象
        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
​
        // 2. 创建sparkContext
        JavaSparkContext sc = new JavaSparkContext(conf);
​
        // 3. 编写代码
        JavaRDD<Integer> intRDD = sc.parallelize(Arrays.asList(4, 56, 7, 8, 1, 2));
​
        // 幸运数字
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
​
        // 找出幸运数字
        // 每一个task都会创建一个list浪费内存
        /*
        JavaRDD<Integer> result = intRDD.filter(new Function<Integer, Boolean>() {
            @Override
            public Boolean call(Integer v1) throws Exception {
                return list.contains(v1);
            }
        });
​
         */
​
        // 创建广播变量
        // 只发送一份数据到每一个executor
        Broadcast<List<Integer>> broadcast = sc.broadcast(list);
​
        JavaRDD<Integer> result = intRDD.filter(new Function<Integer, Boolean>() {
            @Override
            public Boolean call(Integer v1) throws Exception {
                return broadcast.value().contains(v1);
            }
        });
​
​
        result. collect().forEach(System.out::println);
​
​
        // 4. 关闭sc
        sc.stop();
​
    }
}