0802-SparkCore

0802-SparkCore

手写WordCount

0.1 Java

        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("wordcount");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<String> lines = sc.textFile("pom.xml");
        JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public Iterator<String> call(String s) throws Exception {
                return Arrays.asList(s.split("\t")).iterator();
            }
        });
        JavaPairRDD<String, Integer> wordAndOne = words.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });
        JavaPairRDD<String, Integer> reduced = wordAndOne.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });
        JavaPairRDD<Integer, String> swapped = reduced.mapToPair(new PairFunction<Tuple2<String, Integer>, Integer, String>() {
            @Override
            public Tuple2<Integer, String> call(Tuple2<String, Integer> tuple) throws Exception {
                return tuple.swap();
            }
        });
        JavaPairRDD<Integer, String> sorted = swapped.sortByKey(false);
        JavaPairRDD<String, Integer> result = sorted.mapToPair(new PairFunction<Tuple2<Integer, String>, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(Tuple2<Integer, String> tuple) throws Exception {
                return tuple.swap();
            }
        });

        result.saveAsTextFile("2.txt");

        sc.stop();

0.2 Java By Lambda

        SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("wordcount");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<String> lines = sc.textFile("pom.xml");

        JavaRDD<String> words = lines.flatMap(x -> Arrays.asList(x.split("\t")).iterator());
        JavaPairRDD<String, Integer> wordAndOne = words.mapToPair(x -> new Tuple2<>(x, 1));
        JavaPairRDD<String, Integer> reduced = wordAndOne.reduceByKey((x, y) -> x + y);
        JavaPairRDD<Integer, String> swapped = reduced.mapToPair(tuple -> tuple.swap());
        JavaPairRDD<Integer, String> sorted = swapped.sortByKey(false);
        JavaPairRDD<String, Integer> result = sorted.mapToPair(tuple -> tuple.swap());

        result.saveAsTextFile("result");

0.3 Scala

        val conf: SparkConf = new SparkConf().setAppName("DemoWordCount").setMaster("local[*]")
        val sc: SparkContext = new SparkContext(conf)

        val lines: RDD[String] = sc.textFile("E:\\words.txt")
        
        val words: RDD[String] = lines.flatMap(x => x.split(" "))
        val wordAndOne: RDD[(String, Int)] = words.map(x => (x, 1))
        val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey((x, y) => x + y)
        val result: RDD[(String, Int)] = reduced.sortBy(x => x._2)
        result.saveAsTextFile("E:\\output")
        
        sc.stop()
sc.textFile("words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).sortBy(_.2).saveAsTextFile("out")

第一章 RDD概述

1.0 比较Java IO和RDD

1.0.1 Java IO中的装饰者设计模式

在这里插入图片描述

1.0.2 RDD

在这里插入图片描述

1.1 什么是RDD

  1. 官方定义:
    RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据(逻辑)抽象。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合
  2. 通俗理解:
    (1) RDD是Spark中的一个最基本的抽象
    (2) RDD可以认为是一个代理
    (3) 有了RDD,就可以像操作本地的集合一样, 来操作分布式数据集.
    (4) RDD本身不存储数据集合

1.2 RDD的属性

  1. 一组分区(Partition),即数据集的基本组成单位;
  2. 一个计算每个分区的函数;
  3. RDD之间的依赖关系;
  4. 一个Partitioner,即RDD的分片函数;
  5. 一个列表,存储存取每个Partition的优先位置(preferred location)。

1.3 RDD 特点

RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

1.3.1 分区

在这里插入图片描述

1.3.2 只读

在这里插入图片描述

1.3.3 依赖

在这里插入图片描述

1.3.4 缓存

在这里插入图片描述

1.3.5 CheckPoint

第二章 RDD编程

2.0 编程模型

在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
要使用Spark,开发者需要编写一个Driver程序(main),它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。

在这里插入图片描述

2.1 获取SparkContext

  1. scala
	val conf: SparkConf = new SparkConf().setAppName("Demo2").setMaster("local[*]")
	val sc: SparkContext = new SparkContext(conf)
  1. java
	SparkConf conf = new SparkConf().setAppName("Demo01").setMaster("local[*]");
	JavaSparkContext jsc = new JavaSparkContext(conf);

2.2 RDD的创建

在Spark中创建RDD的方式有三种:

  1. 从集合中创建RDD
  2. 从外部存储创建RDD
  3. 从其他RDD创建

2.2.1 从集合中创建

parallelize

val rdd: RDD[Int] = sc.parallelize(1 to 10)

makeRDD

val rdd: RDD[Int] = sc.makeRDD(1 to 10)

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

val lines: RDD[String] = sc.textFile("E:\\agent.log")

2.2.3 从其他RDD创建

2.3 RDD算子之转换算子

2.3.1 value 类型

算子作用
map将func作用于每个元素之后得到一个新的RDD
mapPartition类似于map,但独立地在RDD的每一个分片上运行
mapPartitionWithIndex类似于mapPartitions,但func带有一个整数参数表示分片的索引值
flatMap先map 后 flat
glom将每一个分区形成一个数组,形成新的RDD类型是RDD[Array[T]]
groupBy分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。
filter过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。
sample以指定的随机种子随机抽样出数量为fraction的数据
distinct:对源RDD进行去重后返回一个新的RDD
coalesce缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。
repartition根据分区数,重新通过网络随机洗牌所有数据。
sortBy使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序。
pipe管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。
2.3.1.1 map(func)
  1. 作用:
  2. func
    f: T => U
        /**
         * @MethodName: map(func)
         * @作用: 返回一个新的RDD,该RDD由每一个元素经过func函数转换后组成
         * @需求: 创建一个1-10数组的RDD,将所有元素*2形成新的RDD
         **/
        val rdd: RDD[Int] = sc.makeRDD(1 to 10)
        // f: T => U
        val mapRdd : RDD[Int] = rdd.map(x => x * 10)
2.3.1.2 mapPartition(func)
  1. 作用:
    类似于map, 但独立地在RDD的每一个分片(分区)上运行, 因此在类型为T的RDD上运行时, func的函数类型必须是>Iterator[T] => Iterator[U]
    假设有N个元素, M个分区, 那么map的函数将被调用N次, 而mapPartition被调用M次, 一个函数一次处理所有分区.效率高,但可能会内存溢出
  2. func:
    f: Iterator[T] => Iterator[U]
        /**
         * @MethodName: mapPartitions(func)
         * @作用: 遍历每个分区
         * @需求: 创建一个RDD,使每个元素*2组成新的RDD
         **/
        val rdd2 = sc.makeRDD(Array(1, 2, 3, 4))
        // f: Iterator[T] => Iterator[U]
        val mapPartitionRdd : RDD[Int] = rdd2.mapPartitions(x => x.map(_ * 2)) // x 是一个分区的数据
        // 这里的map是scala的map不是spark的map,
        // executor 上执行的是x.map(_*2)
2.3.1.3 mapPartitionWithIndex(func)
  1. 作用:
    类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];
  2. func
    f: (Int, Iterator[T]) => Iterator[U],
        /**
         * @MethodName: mapPartiotionsWithIndex(func)
         * @作用: 类似于mapPartitions,但func带有一个整数参数表示分片索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int,Interator[T] => Iterator[U])
         * @需求: 创建一个RDD,使每个元素所在分区形成一个元祖组成一个新的RDD
         **/
        val rdd3 = sc.makeRDD(Array(1, 2, 3, 4))
        // f: (Int, Iterator[T]) => Iterator[U],
        val mapPartitionsWithIndexRdd: RDD[(Int, Int)] = rdd3.mapPartitionsWithIndex((index, items) => (items.map((index, _))))
        // 对于多个参数,采用模式匹配写法
        val mapPartitionsWithIndexRdd2: RDD[(Int, String)] = rdd3.mapPartitionsWithIndex {
            case (index, items) => {
                items.map((_, "分区号:" + index))
            }
        }
  • map()和mapPartition()的区别:
  1. map():每次处理一条数据。
  2. mapPartition():每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。
  3. 开发指导:当内存空间较大的时候建议使用mapPartition(),以提高处理效率。
2.3.1.4 flatMap(func)
  1. 作用:
    类似于map,但是每一个输入元素可以被映射为0或多个输出元素,所以func应该返回一个序列,而不是单一元素
  2. func:
    f: T => TraversableOnce[U]
        /**
         * @MethodName: flatMap(func)
         * @作用: 类似于map,但是每一个输入元素可以被映射为0或多个输出元素,所以func应该返回一个序列,而不是单一元素
         * @需求: 创建一个1-5的RDD,运用flatMap创建一个新的RDD,新的RDD为原来的2倍
         **/
        val rdd4 = sc.makeRDD(Array(1, 2, 3, 4, 5))
        // f: T => TraversableOnce[U]
        val flatMapRdd: RDD[Int] = rdd4.flatMap(x => Array(x * 2))
2.3.1.5 glom()
  1. 作用: 以分区为单位做操作, 将每个分区形成一个数组, 形成新的RDD[Array[T]]
  2. func:
        /**
         * @MethodName: glom
         * @作用: 将每一个分区形成一个数组,形成新的RDD类型是RDD[Array[T]]
         * @需求: 创建一个4个分区的RDD,并将每个分区的数据放到一个数组
         **/
        val rdd5 = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8), 4)
        val value: RDD[Array[Int]] = rdd5.glom() 
        //Array[Array[Int]] = Array(Array(1, 2), Array(3, 4), Array(5, 6), Array(7, 8))
2.3.1.6 groupBy(func)
  1. 作用:
    按照传入函数的返回值进行分组, 将相同key对应的值放到一个迭代器中
  2. func:
    f : T => K
        /**
         * @MethodName: groupBy(func)
         * @作用: 分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器
         * @需求: 创建一个RDD,按照元素模以2的值进行分组
         **/
        val rdd6 = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8), 4)
        // f: T => K
        // 分组后的数据形成了元祖(k-v), k表示组的key, v表示分组的数据集合
        val value1: RDD[(Int, Iterable[Int])] = rdd6.groupBy(x => x % 2) 
        //Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4, 6, 8)), (1,CompactBuffer(1, 3, 5, 7)))

2.3.1.7 filter(func)
  1. 作用:
    过滤, 返回一个新的RDD, 该RDD由经过func函数计算后返回值为true的输入元素组成
  2. func:
    f : T => Boolean
        /**
         * @MethodName: filter(func)
         * @作用: 过滤,返回一个新的RDD, 该RDD由经过func函数计算后返回值为true的输入元素组成
         * @需求: 创建一个RDD(由字符串组成) 过滤出一个新的RDD(包含xiao子串)
         **/
        val rdd7 = sc.makeRDD(Array("xiaohahg", "123", "xiaoxiao", "xhxhiaoxiao", "jdj"))
        // f: T => Boolean
        val filterRDD : RDD[String] = rdd7.filter(x => x.contains("xiao"))
2.3.1.8 sample(withReplacement, fraction, seed)
  1. 作用:
    以指定的随机种子随机抽样出数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子。
  2. func :
        /**
         * @MethodName: sample
         * @作用: 以指定的随机种子随机抽样出数据,withReplacement表示是抽出的数据是否放回true为有放回,seed用于指定随机种子
         * @需求: 创建一个RDD(1-10) 从中选择放回和不放回choy
         **/
        val rdd8 = sc.makeRDD(1 to 10)
        val sample1: RDD[Int] = rdd8.sample(true, 0.4, 2)
        val sample2: RDD[Int] = rdd8.sample(false, 0.4, 8)
2.3.1.9 distinct([numTasks])
  1. 作用:
    对源RDD进行去重后返回一个新的RDD,默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它
    一个分区的数据被打乱, 重组. 有shuffle的过程
  2. shuffle:
    将人多的中一个分区的数据打乱.重组到其他不同的分区的操作, 称之为shuffle操作

在这里插入图片描述

        /**
         * @MethodName: distinct([numTasks])
         * @作用: 对源RDD进行去重后返回一个新的RDD,默认情况下,只有8个并任务来操作,但是可以传入一个可选的numTasks参数改变它
         * @需求: 创建一个RDD,去重
         **/
        val rdd9 = sc.makeRDD(Array(1, 2, 3, 4, 3, 2, 3, 32, 3, 2, 5))
        val value2: RDD[Int] = rdd9.distinct()
2.3.1.10 coalesce(numPartitions)
  1. 作用:
    缩减分区数, 用于大数据集过滤后, 提高小数据集的执行效率
  2. 也可以理解为合并分区
    一个分区的数据没有被打乱重组, 没有shuffle过程
        /**
         * @MethodName: coalesce(numPartitions)
         * @作用: 缩减分区数,用于大数据集过滤后, 提高小数据集的执行xiaolv
         * @需求: 创建一个4个分区的RDD,对其缩减分区
         **/
        val rdd10 = sc.makeRDD(1 to 16, 4)
        val value3: RDD[Int] = rdd10.coalesce(3)
2.3.1.11 repartition(numPartitions)
  1. 作用
    根据分区数, 重新通过网络随机洗牌所有数据’’\
  2. 有shuffle过程
        /**
         * @MethodName: repartition(numPartitions)
         * @作用: 根据分区数,重新通过网络随机洗牌所有数据
         * @需求: 创建一个4个分区的RDD,对其重新分区
         **/
        val rdd11 = sc.makeRDD(1 to 20, 4)
        val value4: RDD[Int] = rdd11.repartition(4) 
        // 相当于coalesce(numPartitions = 4,shuffle = true)
2.3.1.12 sortBy(func,[ascending], [numTasks])
  1. 作用:
    使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序
        /**
         * @MethodName: sortBy(func,[ascending],[numTasks])
         * @作用: 使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序
         * @需求: 创建一个RDD,按照不同的规则进行排序
         **/
        val rdd12 = sc.makeRDD(List(2, 1, 3, 4, 6, 3, 5, 2))
        val sorted1: RDD[Int] = rdd12.sortBy(x => x)
        val sorted2: RDD[Int] = rdd12.sortBy(x => x % 2)
2.3.1.13 pipe(command, [envVars])

2.3.2 双value类型

算子作用
union对源RDD和参数RDD求并集后返回一个新的RDD
subtract计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来
intersection对源RDD和参数RDD求交集后返回一个新的RDD
cartesian笛卡尔积(尽量避免使用)
zip将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。
2.3.2.1 union(otherDataset)
2.3.2.2 subtract (otherDataset)
2.3.2.3 intersection(otherDataset)
2.3.2.4 cartesian(otherDataset)
2.3.2.5 zip(otherDataset)

2.3.3 Key-value类型

算子作用
partitionBy对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程。
groupByKeygroupByKey也是对每个key进行操作,但只生成一个sequence。
reduceByKey使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。
aggregateByKey按key将value进行分组合并 ,合并时…
foldByKeyaggregateByKey的简化操作,seqop和combop相同
combineByKey对相同K,把V合并成一个集合。
sortByKey在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
mapValues针对于(K,V)形式的类型只对V进行操作
join在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
cogroup在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
2.3.3.1 partitionBy(partitioner: Partitioner)
  1. 作用:
    对pairRDD进行分区操作, 如果原有partionRDD和现有partionRDD是一致的话就不进行分区, 否则会生成shuffleRDD, 即产生shuffle过程
        /**
          * @MethodName: partitionBy(Partitioner)
          * @作用: 对pairRDD进行分区操作,如果原有的partitionRdd和现有 的partitionRDD是一致的话就不进行分区,否则会生成ShuffleRDD,即会产生shuffle过程
          * @需求: 创建一个4个分区的RDD,对其重新分区
          **/
        val rdd: RDD[(Int, String)] = sc.parallelize(Array((1, "aaa"), (2, "bbb"), (3, "ccc"), (4, "ddd")), 4)
        val value: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
2.3.3.2 groupByKey()
  1. 作用:
    groupByKey也是对每个key进行操作, 但是只生成一个sequence. (key, sequence[T])
        /**
          * @MethodName: groupByKey
          * @作用: 对每个key进行操作,但只生成一个sequence
          * @需求: 创建一个pairRdd,将相同key对应值聚合到一个sequence中,并计算相同key对应值的相加结果
          **/
        val words: Array[String] = Array("one", "two", "two", "three", "three", "three")
        val rdd2: RDD[(String, Int)] = sc.parallelize(words).map(word => (word, 1))
        val grouped: RDD[(String, Iterable[Int])] = rdd2.groupByKey()
        val value1: RDD[(String, Int)] = grouped.map(t => (t._1, t._2.sum))
2.3.3.3 reduceByKey(func, [numTasks])
  1. 作用:
    返回一个KV的RDD, 使用指定的reduce函数, 将相同key的值聚合到一起, reduce任务的个数可以通过第二个可选参数来设置
  2. func:
    f : (V, V) => V
        /**
          * @MethodName: reduceByKey(func,[numTasks])
          * @作用: 在一个(k,v)的RDD上调用,返回一个(k,v)的rdd,使用指定的reduce函数,将相同key聚合到一起,reduce任务的个数可以
          *      通过第二个可选参数来设置
          * @需求: 创建一个pairRDD,计算相同key对应值的相加结果
          **/
        val rdd3 = sc.parallelize(words).map(word => (word, 1))
        // func: (V, V) => V
        val reduceByKeyRdd = rdd3.reduceByKey(_ + _)
  • reduceByKey() 和 groupByKey的区别:
  1. reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].
  2. groupByKey: 按照key进行分组,直接进行shuffle。
  3. 开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。

在这里插入图片描述

2.3.3.4 aggregateByKey(zeroValue,[partitioner]) (seqOp,combOp)
  1. 作用:
    对分区内和分区间的的数据进行不同的操作
  2. 参数:
    (zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
    (1)zeroValue:给每一个分区中的每一个key一个初始值;U
    (2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;(U, V) => U
    (3)combOp:函数用于合并每个分区中的结果。(U, U) => U
        /**
          * @MethodName: aggregateByKey(zeroValue)(func1,func2)
          * @作用: 在kv对的RDD中,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算
          * @需求: 创建一个RDD,取出每个分区相同key对应值的最大值,然后相加
          **/

        val rdd4 = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
        // (zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U)
        val value2: RDD[(String, Int)] = rdd4.aggregateByKey(0)(math.max(_, _), _ + _)
        // 分区1(a,3)(c,4)  分区2(b,3) (c,8)   求和(a,3)(b,3)(c,12)

在这里插入图片描述

2.3.3.5 foldByKey(zeroValue)(func)
  1. 作用,
    aggregateByKey的简化操作,seqop和combop相同
        /**
          * @MethodName: foldByKey(zeroValue)(func)
          * @作用: aggregateByKey 的简化操作,func1和func2 相同
          * @需求:
          **/
        val rdd5 = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
        rdd5.foldByKey(0)(_ + _);
2.3.3.6 combineByKey(createCombiner, mergeValue, mergeCombiners)
  1. 作用:
    对每个key的第一个value进行格式转换
  2. 参数:
    (createCombiner: V => C
    , mergeValue: (C, V) => C
    , mergeCombiners: (C, C) => C)
  3. 参数描述:
    (1)createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值. 将createCombiner函数作用于第一次遇到的key的value
    (2)mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
    (3)mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。
        /**
          * @MethodName: combineByKey[C]
          * @作用: 对每个key的第一个value进行格式转换
          * @需求: 创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及可以对应值的总和,再相除得到结果)
          **/
        val rdd6 = sc.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)), 2)
        val sumAndCount: RDD[(String, (Int, Int))] = rdd6.combineByKey(v => (v, 1)
            , (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1)
            , (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
        
        val result: RDD[(String, Int)] = sumAndCount.map(tuple => (tuple._1, tuple._2._1 / tuple._2._2))
        val result1: RDD[(String, Int)] = sumAndCount.map {
            case (key, value) => (key, value._1 / value._2)
        }

在这里插入图片描述

2.3.3.7 sortByKey([ascending], [numTasks])
  1. 作用:
    在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
        /**
          * @MethodName: sortByKey([ascending],[numTasks])
          * @作用: 作用在一个KV对的RDD上,K必须实现Ordered接口,返回一个按照key进行排序的(k,v)的RDD
          * @需求:
          **/
        val rdd7 = sc.parallelize(Array((3, "aa"), (6, "cc"), (2, "bb"), (1, "dd")))
        val value3: RDD[(Int, String)] = rdd7.sortByKey()
2.3.3.8 mapValues(func)
  1. 作用: 针对于(K,V)形式的类型只对V进行操作
        /**
          * @MethodName: mapValues
          * @作用: (k,v)只针对v进行操作
          * @需求: 创建一个pairRDD,并将value添加字符串“\\”
          **/
        val rdd8 = sc.parallelize(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))
        // f: V => U
        val mapValuesRDD: RDD[(Int, String)] = rdd8.mapValues(x => x + "||")
2.3.3.9 join(otherDataset, [numTasks])
  1. 作用:
    在类型为(K,V)(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
        /**
          * @MethodName: join(otherDataset,[numTasks])
          * @作用: 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
          * @需求: 创建两个pairRDD,并将key相同的数据聚合到一个元组
          **/
        val rdd91 = sc.parallelize(Array((1, "a"), (2, "b"), (3, "c")))
        val rdd92 = sc.parallelize(Array((1, 4), (2, 5), (3, 6)))
        val value4: RDD[(Int, (String, Int))] = rdd91.join(rdd92) 
        //Array[(Int, (String, Int))] = Array((1,(a,4)), (3,(c,6)), (2,(b,5)))
2.3.3.10 cogroup(otherDataset, [numTasks])
  1. 作用:
    在类型为(K,V)(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
        /**
          * @MethodName: cogroup(otherDataset,[numTasks])
          * @作用: 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
          * @需求: 创建两个pairRDD,并将key相同的数据聚合到一个迭代器
          **/
        val rdd101: RDD[(Int, String)] = sc.parallelize(Array((1, "a"), (2, "b"), (3, "c")))
        val rdd102: RDD[(Int, Int)] = sc.parallelize(Array((1, 4), (2, 5), (3, 6)))
        val cogroupRDD: RDD[(Int, (Iterable[String], Iterable[Int]))] = rdd101.cogroup(rdd102)
        //Array((1,(CompactBuffer(a),CompactBuffer(4))), (3,(CompactBuffer(c),CompactBuffer(6))), (2,(CompactBuffer(b),CompactBuffer(5))))

2.3.4 案例实操

  1. 数据结构: 时间戳,省份,城市,用户,广告,中间字段使用空格分割。
1516609143867 6 7 64 16
1516609143869 9 4 75 18
1516609143869 1 7 87 12
  1. 需求:统计出每一个省份广告被点击次数的TOP3
  2. 代码实现:

2.4 RDD算子之行动算子

所有的行动算子最后都会触发sc.runJob

2.4.1 reduce(func)
  1. 作用:
    通过func函数聚合RDD中的所有元素, 先聚合分区内的数据, 再聚合分区间的数据
  2. func:
    f: (T, T) => T
		//需求:创建一个RDD,将所有元素聚合得到结果。
        val rdd1: RDD[Int] = sc.makeRDD(1 to 10, 2)
        val reduced: Int = rdd1.reduce(_ + _)

        val rdd2: RDD[(String, Int)] = sc.makeRDD(Array(("a", 1), ("a", 3), ("c", 3), ("d", 5)))
        val tuple: (String, Int) = rdd2.reduce((x, y) => (x._1, x._2 + y._2))
2.4.2 collect()
  1. 作用:
    在驱动程序中,以数组的形式返回数据集的所有元素。
2.4.3 count()
  1. 作用:返回RDD中元素的个数
2.4.4 first()
2.4.5 take(n)
  1. 作用:返回一个由RDD的前n个元素组成的数组
2.4.6 takeOrdered(n)
  1. 作用:返回该RDD排序后的前n个元素组成的数组
2.4.7 aggregate(zeroValue)(seqOp, combOp)
  1. 作用:
    aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。
  2. 参数:
    (zeroValue: U) (seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
2.4.8 fold(num)(func)
  1. 作用:折叠操作,aggregate的简化操作,seqop和combop一样。
2.4.9 saveAsTextFile(path)
  1. 作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
2.4.10 saveAsSequenceFile(path)
  1. 作用:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
2.4.11 saveAsObjectFile(path)
  1. 作用:用于将RDD中的元素序列化成对象,存储到文件中。
2.4.12 countByKey()
  1. 作用:针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
2.4.13 foreach(func)
  1. 作用:在数据集的每一个元素上,运行函数func进行更新

2.5 RDD中的函数传递(Serializable)

在实际开发中, 往往需要自己定义一些对于RDD的操作, 那么此时最主要的是, 初始化工作是在Diver端进行的, 而实际的计算, 是在Executor端进行的. 这就涉及到了跨进程通信, 是需要序列化的.

2.5.1 传递一个方法

  1. 创建一个类
class Search(query:String){

//过滤出包含字符串的数据
  def isMatch(query: String): Boolean = {
    s.contains(query)
  }

//过滤出包含字符串的RDD
  def getMatch1 (rdd: RDD[String]): RDD[String] = {
    rdd.filter(isMatch)
  }

  //过滤出包含字符串的RDD
  def getMatche2(rdd: RDD[String]): RDD[String] = {
    rdd.filter(x => x.contains(query))
  }
}
  1. 创建Spark主程序
object SeriTest {

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

    //1.初始化配置信息及SparkContext
    val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
    val sc = new SparkContext(sparkConf)

//2.创建一个RDD
    val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))

//3.创建一个Search对象
    val search = new Search()

//4.运用第一个过滤函数并打印结果
    val match1: RDD[String] = search.getMatche1(rdd)
    match1.collect().foreach(println)
    }
}
  1. 运行程序
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
    at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
    at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
    at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
    at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
    at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:387)
    at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:386)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
    at org.apache.spark.rdd.RDD.withScope(RDD.scala:362)
    at org.apache.spark.rdd.RDD.filter(RDD.scala:386)
    at com.atguigu.Search.getMatche1(SeriTest.scala:39)
    at com.atguigu.SeriTest$.main(SeriTest.scala:18)
    at com.atguigu.SeriTest.main(SeriTest.scala)
Caused by: java.io.NotSerializableException: com.atguigu.Search
  1. 问题说明
//过滤出包含字符串的RDD
  def getMatch1 (rdd: RDD[String]): RDD[String] = {
    rdd.filter(isMatch)
  }

在这个方法中, 所调用的方法isMatch()是定义在Search这个类中的. 实际上调用的是this.isMatch(), this就是 new Search() 这个对象, 程序在运行过程中需要将Search对象序列化之后再传递到Executor端.

  1. 解决方案

使类继承scala.Serializable即可。

class Search() extends Serializable{...}

2.5.2 传递一个属性

  1. 创建Spark主程序
object TransmitTest {
  def main(args: Array[String]): Unit = {
    //1.初始化配置信息及SparkContext
    val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
    val sc = new SparkContext(sparkConf)
    //2.创建一个RDD
    val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))
	//3.创建一个Search对象
    val search = new Search()
	//4.运用第一个过滤函数并打印结果
    val match1: RDD[String] = search.getMatche2(rdd)
    match1.collect().foreach(println)
   }
}
  1. 运行程序
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
    at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
    at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
    at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
    at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
    at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:387)
    at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:386)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
    at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
    at org.apache.spark.rdd.RDD.withScope(RDD.scala:362)
    at org.apache.spark.rdd.RDD.filter(RDD.scala:386)
    at com.atguigu.Search.getMatche1(SeriTest.scala:39)
    at com.atguigu.SeriTest$.main(SeriTest.scala:18)
    at com.atguigu.SeriTest.main(SeriTest.scala)
Caused by: java.io.NotSerializableException: com.atguigu.Search
  1. 问题说明
  //过滤出包含字符串的RDD
  def getMatche2(rdd: RDD[String]): RDD[String] = {
    rdd.filter(x => x.contains(query))
  }

在这个方法中所调用的属性query是定义在Search这个类中的字段,实际上调用的是this. query,this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。

  1. 解决方案

1)使类继承scala.Serializable即可。

class Search() extends Serializable{...}

2)将类变量query赋值给局部变量
修改getMatche2为

  //过滤出包含字符串的RDD
  def getMatche2(rdd: RDD[String]): RDD[String] = {
    val query_ : String = this.query//将类变量赋值给局部变量
    rdd.filter(x => x.contains(query_))
    //字符串是可以直接传递的
  }

2.6 RDD依赖关系

每个RDD中都存储着依赖关系

2.6.1 Lineage(血统)

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

2.6.2 窄依赖(独生子女)

窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女
一对一, 多对一

在这里插入图片描述

2.6.3 宽依赖(超生)

宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle,总结:宽依赖我们形象的比喻为超生
一对多, 一个分区的数据被分配到多个分区中, 打乱重组, shuffle

在这里插入图片描述

2.6.4 DAG

DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage
对于窄依赖,partition的转换处理在Stage中完成计算。
对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,
因此宽依赖是划分Stage的依据。

在这里插入图片描述

DAG ===> 依赖关系 ===> Stage

2.6.5 任务划分

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

  1. Application
    初始化一个SparkContext即生成一个Application
  2. Job
    一个Action算子就会生成一个Job
  3. Stage
    根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。
    只有第一个Stage结束, 才能开始第二个Stage, 第三个Stage…
    stage = shuffle + 1 = wide dependency + 1
  4. Task
    Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。
    一个stage最后一个RDD分区个数

在这里插入图片描述

注意:Application->Job->Stage-> Task每一层都是1对n的关系。

2.7 RDD缓存

RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
作用: 将运行时间上 比较重要的数据的计算结果进行缓存, 出错时 不必再从头计算

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

(1)创建一个RDD

scala> val rdd = sc.makeRDD(Array("atguigu"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[19] at makeRDD at <console>:25

(2)将RDD转换为携带当前时间戳不做缓存

scala> val nocache = rdd.map(_.toString+System.currentTimeMillis)
nocache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[20] at map at <console>:27

(3)多次打印结果

scala> nocache.collect
res0: Array[String] = Array(atguigu1538978275359)

scala> nocache.collect
res1: Array[String] = Array(atguigu1538978282416)

scala> nocache.collect
res2: Array[String] = Array(atguigu1538978283199)

(4)将RDD转换为携带当前时间戳并做缓存

scala> val cache =  rdd.map(_.toString+System.currentTimeMillis).cache
cache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[21] at map at <console>:27

(5)多次打印做了缓存的结果

scala> cache.collect
res3: Array[String] = Array(atguigu1538978435705)                                   

scala> cache.collect
res4: Array[String] = Array(atguigu1538978435705)

scala> cache.collect
res5: Array[String] = Array(atguigu1538978435705)

2.8 RDD CheckPoint

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

(1)设置检查点

scala> sc.setCheckpointDir("hdfs://node11:9000/checkpoint")

(2)创建一个RDD

scala> val rdd = sc.parallelize(Array("yanzlh"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[14] at parallelize at <console>:24

(3)将RDD转换为携带当前时间戳并做checkpoint

scala> val ch = rdd.map(_+System.currentTimeMillis)
ch: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[16] at map at <console>:26

scala> ch.checkpoint

(4)多次打印结果

scala> ch.collect
res55: Array[String] = Array(yanzlh1538981860336)

scala> ch.collect
res56: Array[String] = Array(yanzlh1538981860504)

scala> ch.collect
res57: Array[String] = Array(yanzlh1538981860504)

scala> ch.collect
res58: Array[String] = Array(yanzlh1538981860504)

第三章 键值对RDD数据分区器

Spark目前支持Hash分区和Range分区,用户也可以自定义分区,Hash分区为当前的默认分区,Spark中分区器直接决定了RDD中分区的个数RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数.
注意:
(1)只有Key-Value类型的RDD才有分区器的,非Key-Value类型的RDD分区器的值是None
(2)每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。

3.0 获取RDD分区

可以通过使用RDD的partitioner 属性来获取 RDD 的分区方式。它会返回一个 scala.Option 对象, 通过get方法获取其中的值

3.1 Hash 分区

3.2 Ranger 分区

3.3 自定义分区

要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个方法。
(1)numPartitions: Int:返回创建出来的分区数
(2)getPartition(key: Any): Int:返回给定键的分区编号(0 到 numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同。

第四章 数据读取与保存

4.1 Text文件

  1. 数据读取: textFile(String)
  2. 数据保存: saveAsTextFile(String)

4.2 JSON文件

使用RDD读取JSON文件处理很复杂,同时SparkSQL集成了很好的处理JSON文件的方式,所以应用中多是采用SparkSQL处理JSON文件。

4.3 Sequence文件

SequenceFile文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(Flat File)。Spark 有专门用来读取 SequenceFile 的接口。在 SparkContext 中,可以调用 sequenceFile[keyClass, valueClass ](path)。
注意:SequenceFile文件只针对PairRDD

  1. 数据读取: sc.sequenceFile(String)
  2. 数据保存: rdd.saveAsSequenceFile(String)

4.4 对象文件

对象文件是将对象序列化后保存的文件,采用Java的序列化机制。可以通过objectFilek,v 函数接收一个路径,读取对象文件,返回对应的 RDD,也可以通过调用saveAsObjectFile() 实现对对象文件的输出。因为是序列化所以要指定类型。

  1. 数据读取: sc.objectFile(String)
  2. 数据保存: rdd.saveAsObjectFile(String)

4.5 HDFS

4.6 MySQL

支持通过Java JDBC访问关系型数据库。需要通过JdbcRDD进行,示例如下:

(1)添加依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.27</version>
</dependency>

(2)Mysql读取:

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

        //1.创建spark配置信息
        val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JdbcRDD")

        //2.创建SparkContext
        val sc = new SparkContext(sparkConf)

        //3.定义连接mysql的参数
        val driver = "com.mysql.jdbc.Driver"
        val url = "jdbc:mysql://node11:3306/rdd"
        val userName = "root"
        val passWd = "1229"

        //创建JdbcRDD
        val rdd: JdbcRDD[(Int, String)] = new JdbcRDD(sc, () => {
            Class.forName(driver)
            DriverManager.getConnection(url, userName, passWd)
        },
            "select * from `rddtable` where `id`>=?;",
            1,
            10,
            1,
            r => (r.getInt(1), r.getString(2))
        )

        //打印最后结果
        println(rdd.count())
        rdd.foreach(println)

        rdd.foreachPartition(insertData)

        sc.stop()
    }

(3)Mysql写入:

    def insertData(iterator: Iterator[String]) : Unit = {
        Class.forName ("com.mysql.jdbc.Driver").newInstance()
        val conn = java.sql.DriverManager.getConnection("jdbc:mysql://hadoop102:3306/rdd", "root", "000000")
        iterator.foreach(data => {
            val ps = conn.prepareStatement("insert into rddtable(name) values (?)")
            ps.setString(1, data)
            ps.executeUpdate()
        })
    }
    // 调用
    val data: RDD[String] = sc.parallelize(List("Female", "Male", "Female"))
    data.foreachPartition(insertData)

4.7 HBase

(1)添加依赖

<dependency>
	<groupId>org.apache.hbase</groupId>
	<artifactId>hbase-server</artifactId>
	<version>1.3.1</version>
</dependency>

<dependency>
	<groupId>org.apache.hbase</groupId>
	<artifactId>hbase-client</artifactId>
	<version>1.3.1</version>
</dependency>

(2)从HBase读取数据

package com.atguigu

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.hadoop.hbase.util.Bytes

object HBaseSpark {

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

    //创建spark配置信息
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JdbcRDD")

    //创建SparkContext
    val sc = new SparkContext(sparkConf)

    //构建HBase配置信息
    val conf: Configuration = HBaseConfiguration.create()
    conf.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104")
    conf.set(TableInputFormat.INPUT_TABLE, "rddtable")

    //从HBase读取数据形成RDD
    val hbaseRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
      conf,
      classOf[TableInputFormat],
      classOf[ImmutableBytesWritable],
      classOf[Result])

    val count: Long = hbaseRDD.count()
    println(count)

    //对hbaseRDD进行处理
    hbaseRDD.foreach {
      case (_, result) =>
        val key: String = Bytes.toString(result.getRow)
        val name: String = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name")))
        val color: String = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("color")))
        println("RowKey:" + key + ",Name:" + name + ",Color:" + color)
    }

    //关闭连接
    sc.stop()
  }

}

3)往HBase写入

def main(args: Array[String]) {
//获取Spark配置信息并创建与spark的连接
  val sparkConf = new SparkConf().setMaster("local[*]").setAppName("HBaseApp")
  val sc = new SparkContext(sparkConf)

//创建HBaseConf
  val conf = HBaseConfiguration.create()
  val jobConf = new JobConf(conf)
  jobConf.setOutputFormat(classOf[TableOutputFormat])
  jobConf.set(TableOutputFormat.OUTPUT_TABLE, "fruit_spark")

//构建Hbase表描述器
  val fruitTable = TableName.valueOf("fruit_spark")
  val tableDescr = new HTableDescriptor(fruitTable)
  tableDescr.addFamily(new HColumnDescriptor("info".getBytes))

//创建Hbase表
  val admin = new HBaseAdmin(conf)
  if (admin.tableExists(fruitTable)) {
    admin.disableTable(fruitTable)
    admin.deleteTable(fruitTable)
  }
  admin.createTable(tableDescr)

//定义往Hbase插入数据的方法
  def convert(triple: (Int, String, Int)) = {
    val put = new Put(Bytes.toBytes(triple._1))
    put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(triple._2))
    put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("price"), Bytes.toBytes(triple._3))
    (new ImmutableBytesWritable, put)
  }

//创建一个RDD
  val initialRDD = sc.parallelize(List((1,"apple",11), (2,"banana",12), (3,"pear",13)))

//将RDD内容写到HBase
  val localData = initialRDD.map(convert)

  localData.saveAsHadoopDataset(jobConf)
}

第五章 RDD编程进阶

5.0 Spark中的数据结构

5.0.1 RDD: 分布式数据集

5.0.2 累加器: 分布式只写共享变量

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

5.1 累加器

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

5.1.1 系统累加器

针对一个输入的日志文件,如果我们想计算文件中所有空行的数量,我们可以编写以下程序:

    val notice = sc.textFile("./NOTICE")
    // 定义累加器变量
    val blanklines = sc.accumulator(0)
    val tmp = notice.flatMap(line => {
        if (line == "") {
            blanklines += 1
        }
        line.split(" ")
    })
    tmp.count()
    // 获取累加器变量的值
    blanklines.value

5.1.2 自定义累加器

package com.lz.spark.day02

import org.apache.spark.{SparkConf, SparkContext}

/**
 * @ClassName LogAccumulator
 * @Description: TODO 
 * @Author MAlone
 * @Date 2019/12/16 
 * @Version V1.0
 **/
class LogAccumulator extends org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] {
    private val _logArray: java.util.Set[String] = new java.util.HashSet[String]()

    override def isZero: Boolean = {
        _logArray.isEmpty
    }

    override def reset(): Unit = {
        _logArray.clear()
    }

    override def add(v: String): Unit = {
        _logArray.add(v)
    }

    override def merge(other: org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]]): Unit = {
        other match {
            case o: LogAccumulator => _logArray.addAll(o.value)
        }

    }

    override def value: java.util.Set[String] = {
        java.util.Collections.unmodifiableSet(_logArray)
    }

    override def copy():org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] = {
        val newAcc = new LogAccumulator()
        _logArray.synchronized{
            newAcc._logArray.addAll(_logArray)
        }
        newAcc
    }
}

// 过滤掉带字母的
object LogAccumulator {
    def main(args: Array[String]) {
        val conf=new SparkConf().setAppName("LogAccumulator")
        val sc=new SparkContext(conf)

        val accum = new LogAccumulator
        sc.register(accum, "logAccum")
        val sum = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7cd", "8", "9"), 2).filter(line => {
            val pattern = """^-?(\d+)"""
            val flag = line.matches(pattern)
            if (!flag) {
                accum.add(line)
            }
            flag
        }).map(_.toInt).reduce(_ + _)

        println("sum: " + sum)
        for (v <- accum.value) print(v + "")
        println()
        sc.stop()
    }
}

5.2 广播变量(调优策略)

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

第六章 RDD相关概率关系

在这里插入图片描述

输入可能以多个文件的形式存储在HDFS上,每个File都包含了很多块,称为Block。当Spark读取这些文件作为输入时,会根据具体数据格式对应的InputFormat进行解析,一般是将若干个Block合并成一个输入分片,称为InputSplit,注意InputSplit不能跨越文件。随后将为这些输入分片生成具体的Task。InputSplit与Task是一一对应的关系。随后这些具体的Task每个都会被分配到集群上的某个节点的某个Executor去执行。

  1. 每个节点可以起一个或多个Executor。
  2. 每个Executor由若干core组成,每个Executor的每个core一次只能执行一个Task。
  3. 每个Task执行的结果就是生成了目标RDD的一个partiton。 注意:
    这里的core是虚拟的core而不是机器的物理CPU核,可以理解为就是Executor的一个工作线程。而 Task被执行的并发度 =Executor数目 * 每个Executor核数。

至于partition的数目

  1. 对于数据读入阶段,例如sc.textFile,输入文件被划分为多少InputSplit就会需要多少初始Task
  2. 在Map阶段partition数目保持不变。
  3. 在Reduce阶段,RDD的聚合会触发shuffle操作,聚合后的RDD的partition数目跟具体操作有关,例如repartition操作会聚合成指定分区数,还有一些算子是可配置的。

RDD在计算的时候,每个分区都会起一个task,所以rdd的分区数目决定了总的的task数目。申请的计算节点(Executor)数目和每个计算节点核数,决定了你同一时刻可以并行执行的task。
比如的RDD有100个分区,那么计算的时候就会生成100个task,你的资源配置为10个计算节点,每个两2个核,同一时刻可以并行的task数目为20,计算这个RDD就需要5个轮次。如果计算资源不变,你有101个task的话,就需要6个轮次,在最后一轮中,只有一个task在执行,其余核都在空转。如果资源不变,你的RDD只有2个分区,那么同一时刻只有2个task运行,其余18个核空转,造成资源浪费。这就是在spark调优中,增大RDD分区数目,增大任务并行度的做法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值