SparkCore简介

SparkCore简介

@

目录

一.RDD概述

1,RDD介绍

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的抽象数据
RDD代表一个弹性的不可变的不可分区包含的元素可以并行计算的集合

2,RDD五大特性

  1. 一组分区(Partition),即是数据集的基本组成单位,标记数据是哪个分区的
  2. 一个计算每个分区的函数
  3. RDD之间拥有依赖关系
  4. 一个Partition,即一个RDD的分区,控制分区的数据流向(键值对)
  5. 一个列表,存储存取每个Partition的优先位置

二.RDD的创建

1,环境准备

本人用的是IDEA 2018版本

  1. 创建一个maven工程
  2. 创建一个scala文件夹,并设置为Source Root
  3. 在pom文件中添加spark-core的依赖和scala的编译插件
<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.12</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>

<build>
    <finalName>SparkCoreTest</finalName>
    <plugins>
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.4.6</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2,RDD创建的方式

RDD一共有三种创建方式:从集合内创建RDD,从外部存储创建RDD,从其他RDD创建RDD(存在依赖关系)

(1)集合内创建
object createrdd01_array {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.使用parallelize()创建rdd
        val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8))

        rdd.collect().foreach(println)

        //4.使用makeRDD()创建rdd(后续介绍都将用 makeRDD 来操作)
        val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8))

        rdd1.collect().foreach(println)

        sc.stop()
    }
}

注意:makeRDD有两种重构方法,重构方法一如下,makeRDD和parallelize功能一样。

def makeRDD[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
  parallelize(seq, numSlices)
}

makeRDD的重构方法二,增加了位置信息
注意:只需要知道makeRDD不完全等于parallelize即可。

def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T] = withScope {
    assertNotStopped()
    val indexToPrefs = seq.zipWithIndex.map(t => (t._2, t._1._2)).toMap
    new ParallelCollectionRDD[T](this, seq.map(_._1), math.max(seq.size, 1), indexToPrefs)
}
(2)从外部存储的数据集创建

由外部存储系统的数据集创建RDD包括:本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、HBase等。

object createrdd02_file {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.读取文件。
        val lineWordRdd: RDD[String] = sc.textFile("input//test.txt")

        //4.打印
        lineWordRdd.foreach(println)

        //5.关闭
        sc.stop()
    }
}
(3)从其他RDD创建RDD

由前一个RDD运算后得到新的RDD

2,分区规则

(1)默认分区

默认分区源码
在这里插入图片描述
代码实现:

object partition01_Array_default {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        val rdd: RDD[Int] = sc.makeRDD(Array(1,2,3,4))

        //3. 输出数据,产生了16个分区
        rdd.saveAsTextFile("output")
         //结论:从集合创建rdd,如果不手动写分区数量的情况下,默认分区数跟本地模式的cpu核数有关
         //local : 1个   local[*] : 笔记本所有核心数    local[K]:K个

        //4.关闭连接
        sc.stop()
    }
}
(2)自定义分区
object partition02_Array {

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

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

        //1)4个数据,设置4个分区,输出:0分区->1,1分区->2,2分区->3,3分区->4
        //val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 4)

        //2)4个数据,设置3个分区,输出:0分区->1,1分区->2,2分区->3,4
        //val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 3)

        //3)5个数据,设置3个分区,输出:0分区->1,1分区->2、3,2分区->4、5
        val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5), 3)

        rdd.saveAsTextFile("output")

        sc.stop()
    }
}

分区源码:
在这里插入图片描述

三.Transformation 转换算子

1,Value类型

map()映射
函数签名:def map[U:ClassTag](f:T=>U):RDD[U]

代码实现:

object value01_map {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)

        // 3.2 调用map方法,每个元素乘以2
        val mapRdd: RDD[Int] = rdd.map(_ * 2)

        // 3.3 打印修改后的RDD中数据
        mapRdd.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
mapPartition()以分区为单位进行map操作
函数签名:
def mapPartition[U:ClassTag](
	f:Iterator[T]=>Iterator[U],
	preservesPartitioning:Boolean=false
):RDD[U]

实现代码:

object value02_mapPartitions {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)

        // 3.2 调用mapPartitions方法,每个元素乘以2
        val rdd1 = rdd.mapPartitions(x=>x.map(_*2))

        // 3.3 打印修改后的RDD中数据
        rdd1.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
mapPartitionWithIndex() 带分区号
函数签名:
      def mapPartitionsWithIndex[U: ClassTag](
      	f: (Int, Iterator[T]) => Iterator[U], // Int表示分区编号
      	preservesPartitioning: Boolean = false): RDD[U]

代码实现:

object value03_mapPartitionsWithIndex {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(1 to 4, 2)

        // 3.2 创建一个RDD,使每个元素跟所在分区号形成一个元组,组成一个新的RDD
        val indexRdd = rdd.mapPartitionsWithIndex( (index,items)=>{items.map( (index,_) )} )

        // 3.3 打印修改后的RDD中数据
        indexRdd.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
flatMap()扁平化
函数签名:def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]

代码实现:

object value04_flatMap {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val listRDD=sc.makeRDD(List(List(1,2),List(3,4),List(5,6),List(7)), 2)

        // 3.2 把所有子集合中数据取出放入到一个大的集合中
        listRDD.flatMap(list=>list).collect.foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
glom()分区转换数组
函数签名:def glom():RDD[Array[T]]

代码实现:

object value05_glom {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd = sc.makeRDD(1 to 4, 2)

        // 3.2 求出每个分区的最大值  0->1,2   1->3,4
        val maxRdd: RDD[Int] = rdd.glom().map(_.max)

        // 3.3 求出所有分区的最大值的和 2 + 4
        println(maxRdd.collect().sum)

        //4.关闭连接
        sc.stop()
    }
}
groupby()分组
函数签名:def groupBy[K](f:T=>K)(implicit kt:ClassTag[K]):RDD[(K,Iterable[T])]

代码实现:

object value06_groupby {

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

        //1.创建SparkConf并设置App名称
        val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd = sc.makeRDD(1 to 4, 2)

        // 3.2 将每个分区的数据放到一个数组并收集到Driver端打印
        rdd.groupBy(_ % 2).collect().foreach(println)

        // 3.3 创建一个RDD
        val rdd1: RDD[String] = sc.makeRDD(List("hello","hive","hadoop","spark","scala"))

        // 3.4 按照首字母第一个单词相同分组
        rdd1.groupBy(str=>str.substring(0,1)).collect().foreach(println)

        sc.stop()
    }
}

groupBy会存在shuffle过程
shuffle:将不同的分区数据进行打乱重组的过程
shuffle一定会落盘。可以在local模式下执行程序,通过4040看效果。

filter()过滤
函数签名: def filter(f: T => Boolean): RDD[T]

代码实现:

object value07_filter {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 2)

        //3.1 过滤出符合条件的数据
        val filterRdd: RDD[Int] = rdd.filter(_ % 2 == 0)

        //3.2 收集并打印数据
        filterRdd.collect().foreach(println)

        //4 关闭连接
        sc.stop()
    }
}
sample()采样
函数签名:
       def sample(
            withReplacement: Boolean,
            fraction: Double,
            seed: Long = Utils.random.nextLong): RDD[T]

代码实现:

object value08_sample {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 创建一个RDD
        val dataRDD: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6))

        // 抽取数据不放回(伯努利算法)
        // 伯努利算法:又叫0、1分布。例如扔硬币,要么正面,要么反面。
        // 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要
        // 第一个参数:抽取的数据是否放回,false:不放回
        // 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
        // 第三个参数:随机数种子
        val sampleRDD: RDD[Int] = dataRDD.sample(false, 0.5)
        sampleRDD.collect().foreach(println)

        println("----------------------")

        // 抽取数据放回(泊松算法)
        // 第一个参数:抽取的数据是否放回,true:放回;false:不放回
        // 第二个参数:重复数据的几率,范围大于等于0.表示每一个元素被期望抽取到的次数
        // 第三个参数:随机数种子
        val sampleRDD1: RDD[Int] = dataRDD.sample(true, 2)
        sampleRDD1.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
distinct()去重
函数签名:def distinct():RDD[T]

代码实现:

object value09_distinct {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val distinctRdd: RDD[Int] = sc.makeRDD(List(1,2,1,5,2,9,6,1))

        // 3.2 打印去重后生成的新RDD
        distinctRdd.distinct().collect().foreach(println)

        // 3.3 对RDD采用多个Task去重,提高并发度
        distinctRdd.distinct(2).collect().foreach(println)

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

distinct会存在shuffle过程

coalesce()合并分区

1,不执行shuffle方法

def coalesce(numPartitions: Int, shuffle: Boolean = false,  //默认false不执行shuffle
        partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
        (implicit ord: Ordering[T] = null) : RDD[T]

代码实现:

object value10_coalesce {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.创建一个RDD
        //val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4), 4)

        //3.1 缩减分区
        //val coalesceRdd: RDD[Int] = rdd.coalesce(2)

        //4. 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
        //4.1 缩减分区
        val coalesceRDD: RDD[Int] = rdd.coalesce(2)

        //5 查看对应分区数据
        val indexRDD: RDD[(Int, Int)] = coalesceRDD.mapPartitionsWithIndex(
            (index, datas) => {
                datas.map((index, _))
            }
        )

        //6 打印数据
        indexRDD.collect().foreach(println)

//8 延迟一段时间,观察http://localhost:4040页面,查看Shuffle读写数据
Thread.sleep(100000)

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

2,执行shuffle方法

//3. 创建一个RDD
val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)
//3.1 执行shuffle
val coalesceRdd: RDD[Int] = rdd.coalesce(2, true)
repartition()重新分区(执行shuffle)
函数签名: def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

代码实现:

object value11_repartition {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3. 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6), 3)

        //3.1 缩减分区
        //val coalesceRdd: RDD[Int] = rdd.coalesce(2, true)

        //3.2 重新分区
        val repartitionRdd: RDD[Int] = rdd.repartition(2)

        //4 打印查看对应分区数据
        val indexRdd: RDD[(Int, Int)] = repartitionRdd.mapPartitionsWithIndex(
            (index, datas) => {
                datas.map((index, _))
            }
        )

        //5 打印
        indexRdd.collect().foreach(println)

        //6. 关闭连接
        sc.stop()
    }
}
sortBy()排序
函数签名:
def sortBy[K]( f: (T) => K,
      ascending: Boolean = true, // 默认为正序排列
      numPartitions: Int = this.partitions.length)
      (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]

代码实现:

object value12_sortBy {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        // 3.1 创建一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(2, 1, 3, 4, 6, 5))

        // 3.2 默认是升序排
        val sortRdd: RDD[Int] = rdd.sortBy(num => num)
        sortRdd.collect().foreach(println)

        // 3.3 配置为倒序排
        val sortRdd2: RDD[Int] = rdd.sortBy(num => num, false)
        sortRdd2.collect().foreach(println)

        // 3.4 创建一个RDD
        val strRdd: RDD[String] = sc.makeRDD(List("1", "22", "12", "2", "3"))

        // 3.5 按照字符的int值排序
        strRdd.sortBy(num => num.toInt).collect().foreach(println)

        // 3.5 创建一个RDD
        val rdd3: RDD[(Int, Int)] = sc.makeRDD(List((2, 1), (1, 2), (1, 1), (2, 2)))

        // 3.6 先按照tuple的第一个值排序,相等再按照第2个值排
        rdd3.sortBy(t=>t).collect().foreach(println)

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

2,双Value类型交互

intersection()交集
函数签名:def intersection(other:RDD[T]):RDD[T]

代码实现:

object DoubleValue01_intersection {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd1: RDD[Int] = sc.makeRDD(1 to 4)

        //3.2 创建第二个RDD
        val rdd2: RDD[Int] = sc.makeRDD(4 to 8)

        //3.3 计算第一个RDD与第二个RDD的交集并打印
        rdd1.intersection(rdd2).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
union()并集不去重
函数签名:def union(other:RDD[T]):RDD[T]

代码实现:

object DoubleValue02_union {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd1: RDD[Int] = sc.makeRDD(1 to 4)

        //3.2 创建第二个RDD
        val rdd2: RDD[Int] = sc.makeRDD(4 to 8)

        //3.3 计算两个RDD的并集,会有两个4
        rdd1.union(rdd2).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
subtract()差集
函数签名:def subtract(other:RDD[T]):RDD[T]

代码实现:

object DoubleValue03_subtract {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(1 to 4)

        //3.2 创建第二个RDD
        val rdd1: RDD[Int] = sc.makeRDD(4 to 8)

        //3.3 计算第一个RDD与第二个RDD的差集并打印
        rdd.subtract(rdd1).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
zip()拉链
函数签名:def zip[U:ClassTag](other:RDD[U]):RDD[(T,U)]

代码实现:

object DoubleValue04_zip {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd1: RDD[Int] = sc.makeRDD(Array(1,2,3),3)

        //3.2 创建第二个RDD
        val rdd2: RDD[String] = sc.makeRDD(Array("a","b","c"),3)

        //3.3 第一个RDD组合第二个RDD并打印
        rdd1.zip(rdd2).collect().foreach(println)

        //3.4 第二个RDD组合第一个RDD并打印
        rdd2.zip(rdd1).collect().foreach(println)

        //3.5 创建第三个RDD(与1,2分区数不同)
        val rdd3: RDD[String] = sc.makeRDD(Array("a","b"), 3)

        //3.6 元素个数不同,不能拉链
        // Can only zip RDDs with same number of elements in each partition
        rdd1.zip(rdd3).collect().foreach(println)

        //3.7 创建第四个RDD(与1,2分区数不同)
        val rdd4: RDD[String] = sc.makeRDD(Array("a","b","c"), 2)

        //3.8 分区数不同,不能拉链
        // Can't zip RDDs with unequal numbers of partitions: List(3, 2)
        rdd1.zip(rdd4).collect().foreach(println)

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

3,Key-Value类型

partitionBy()按照key值重新分区
函数签名:def partitionBy(partitioner:Partitioner):RDD[(K,V)]

代码实现:

object KeyValue01_partitionBy {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"aaa"),(2,"bbb"),(3,"ccc")),3)

        //3.2 对RDD重新分区
        val rdd2: RDD[(Int, String)] = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))

        //3.3 打印查看对应分区数据  (0,(2,bbb))  (1,(1,aaa))  (1,(3,ccc))
        val indexRdd = rdd2.mapPartitionsWithIndex(
            (index, datas) => datas.map((index,_))
        )
        indexRdd.collect().foreach(println)

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

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

object KeyValue01_partitionBy {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)

        //3.2 自定义分区
        val rdd3: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(2))

        //4 打印查看对应分区数据
        val indexRdd = rdd3.mapPartitionsWithIndex(
            (index, datas) => datas.map((index,_))
        )

        indexRdd.collect()

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

// 自定义分区
class MyPartitioner(num: Int) extends Partitioner {

    // 设置的分区数
    override def numPartitions: Int = num

    // 具体分区逻辑
    override def getPartition(key: Any): Int = {

        if (key.isInstanceOf[Int]) {

            val keyInt: Int = key.asInstanceOf[Int]
            if (keyInt % 2 == 0)
                0
            else
                1
        }else{
            0
        }
    }
}
reduceByKey()按照key聚合v
函数签名:
	def reduceByKey(func:(v,v)=>v):RDD[(k,v)]
	def reduceByKey(func:(v,v)=>v,numPartitions:Int):RDD[(K,V)]

代码实现:

object KeyValue02_reduceByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd = sc.makeRDD(List(("a",1),("b",5),("a",5),("b",2)))

        //3.2 计算相同key对应值的相加结果
        val reduce: RDD[(String, Int)] = rdd.reduceByKey((v1,v2) => v1+v2)

        //3.3 打印结果
        reduce.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
groupByKey()按照key值重新分组
函数签名:def groupByKey():RDD[(K,iterable[V])]

代码实现:

object KeyValue03_groupByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd = sc.makeRDD(List(("a",1),("b",5),("a",5),("b",2)))

        //3.2 将相同key对应值聚合到一个Seq中
        val group: RDD[(String, Iterable[Int])] = rdd.groupByKey()
        
        //3.3 打印结果
        group.collect().foreach(println)
        
        //3.4 计算相同key对应值的相加结果
        group.map(t=>(t._1,t._2.sum)).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
aggregateByKey()按照key处理分区内和分区间的逻辑
函数签名:def aggregateByKey[U:ClassTag](zeroValue:U)(seqOp:(U,V)=>U,combOp:(U,U)=>U):RDD[(K,V)]

1,zeroValue(初始值):给每个分区中的每一种key一个初始值;
2,seqOp(分区内):函数用于在每一个分区中用初始值逐步迭代value
3,combOp(分区间):函数用于合并每个分区中的结果

代码实现:

object KeyValue04_aggregateByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a",1),("a",3),("a",5),("b",7),("b",2),("b",4),("b",6),("a",7)), 2)

        //3.2 取出每个分区相同key对应值的最大值,然后相加
        rdd.aggregateByKey(0)(math.max(_, _), _ + _).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
foldByKey()分区内和分区间相同的aggregateByKey()(简化版)
函数签名:
	def foldByKey(zeroValue:V)(func:(V,V)=>V):RDD[(k,v)]

代码实现:

object KeyValue05_foldByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val list: List[(String, Int)] = List(("a",1),("a",3),("a",5),("b",7),("b",2),("b",4),("b",6),("a",7))
        val rdd = sc.makeRDD(list,2)

        //3.2 求wordcount
        //rdd.aggregateByKey(0)(_+_,_+_).collect().foreach(println)

        rdd.foldByKey(0)(_+_).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
combineByKey()转换结构后分区内和分区间操作
函数签名:
	def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C): RDD[(K, C)]

(1)createCombiner(转换数据的结构): combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值
(2)mergeValue(分区内): 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
(3)mergeCombiners(分区间): 由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的mergeCombiners()方法将各个分区的结果进行合并。

代码实现:

object KeyValue06_combineByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.1 创建第一个RDD
        val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
        val rdd: RDD[(String, Int)] = sc.makeRDD(list, 2)

        //3.2 将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组
        val combineRdd: RDD[(String, (Int, Int))] = rdd.combineByKey(
            (_, 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)
        )

        //3.3 打印合并后的结果
        combineRdd.collect().foreach(println)

        //3.4 计算平均值
        combineRdd.map {
            case (key, value) => {
                (key, value._1 / value._2.toDouble)
            }
        }.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
reduceByKey、foldByKey、aggregateByKey、combineByKey的区别

在这里插入图片描述

sortByKey()按照key进行排序
函数签名:
	def sortByKey(
       ascending: Boolean = true, // 默认,升序
       numPartitions: Int = self.partitions.length)  : RDD[(K, V)]

代码实现:

object KeyValue07_sortByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))

        //3.2 按照key的正序(默认顺序)
        rdd.sortByKey(true).collect().foreach(println)

        //3.3 按照key的倒序
        rdd.sortByKey(false).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
mapValue()只对v进行操作
函数签名:
	def mapValue[U](f:V=>U):RDD[(K,U)]

代码实现:

object KeyValue08_mapValues {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))

        //3.2 对value添加字符串"|||"
        rdd.mapValues(_ + "|||").collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
join()等同于sql里的内连接,关联上的要,关联不上的舍弃
函数签名:
	def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
	def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] 

代码实现:

object KeyValue09_join {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))

        //3.2 创建第二个pairRDD
        val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))

        //3.3 join操作并打印结果
        rdd.join(rdd1).collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
cogroup()类似sql的全连接,但是在同一个RDD中对key集合
函数签名:
	def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]

代码实现:

bject KeyValue10_cogroup {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c")))

        //3.2 创建第二个RDD
        val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(4,6)))

        //3.3 cogroup两个RDD并打印结果
// (1,(CompactBuffer(a),CompactBuffer(4)))
// (2,(CompactBuffer(b),CompactBuffer(5)))
// (3,(CompactBuffer(c),CompactBuffer()))
// (4,(CompactBuffer(),CompactBuffer(6)))
        rdd.cogroup(rdd1).collect().foreach(println)

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

4,Action行动算子

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

reduce()聚合
函数签名:def reduce(f:(T,T)=>T):T

代码实现:

object action01_reduce {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 聚合数据
        val reduceResult: Int = rdd.reduce(_+_)
        println(reduceResult)

        //4.关闭连接
        sc.stop()
    }
}
collect()以数组的形式返回数据集
函数签名:def collect():Array[T]

代码实现:

object action02_collect {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 收集数据到Driver
        rdd.collect().foreach(println)

        //4.关闭连接
        sc.stop()
    }
}
count()返回RDD中元素个数
函数签名:def count():Long

代码实现:

object action03_count {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 返回RDD中元素的个数
        val countResult: Long = rdd.count()
        println(countResult)

        //4.关闭连接
        sc.stop()
    }
}
first()返回RDD中的第一个元素
函数签名:def first():T

代码实现:

object action04_first {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 返回RDD中元素的个数
        val firstResult: Int = rdd.first()
        println(firstResult)

        //4.关闭连接
        sc.stop()
    }
}
take()返回由RDD前n个元素组成的数组
函数签名:def take(num:Int):Array[T]

代码实现:

object action05_take {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 返回RDD中前2个元素
        val takeResult: Array[Int] = rdd.take(2)
        println(takeResult.mkString(","))

        //4.关闭连接
        sc.stop()
    }
}
takeordered()返回该RDD排序后前n个元素组成的数组
函数签名:def takeOrdered(num:Int)(implicit ord:Ordering[T]):Array[T]

源码展示:

def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T] = withScope {
    ......
    if (mapRDDs.partitions.length == 0) {
        Array.empty
    } else {
        mapRDDs.reduce { (queue1, queue2) =>
          queue1 ++= queue2
          queue1
        }.toArray.sorted(ord)
    }
}

代码实现:

object action06_takeOrdered{

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1,3,2,4))

        //3.2 返回RDD中排完序后的前两个元素
        val result: Array[Int] = rdd.takeOrdered(2)
        println(result.mkString(","))

        //4.关闭连接
        sc.stop()
    }
}
aggregate()案例
函数签名:def aggregate[U:ClassTag](zeroValue:U)(seqOp:(U,T)=>U,combOp:(U,U)=>U):U

代码实现:

object action07_aggregate {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 8)

        //3.2 将该RDD所有元素相加得到结果
        //val result: Int = rdd.aggregate(0)(_ + _, _ + _)
        val result: Int = rdd.aggregate(10)(_ + _, _ + _)

        println(result)

        //4.关闭连接
        sc.stop()
    }
}
fold()案例
函数签名:def fold(zeroValue:T)(op:(T,T)=>T):T

代码实现:

object action08_fold {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))

        //3.2 将该RDD所有元素相加得到结果
        val foldResult: Int = rdd.fold(0)(_+_)
        println(foldResult)

        //4.关闭连接
        sc.stop()
    }
}
countByKey()统计每种key的个数
函数签名:def countByKey():Map[K,Long]

代码实现:

object action09_countByKey {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        val rdd: RDD[(Int, String)] = sc.makeRDD(List((1, "a"), (1, "a"), (1, "a"), (2, "b"), (3, "c"), (3, "c")))

        //3.2 统计每种key的个数
        val result: collection.Map[Int, Long] = rdd.countByKey()
        println(result)

        //4.关闭连接
        sc.stop()
    }
}
save算子

功能说明:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本

saveAsSequenceFile(path) 保存成Sequencefile文件

功能说明:将数据集中的元素以Hadoop Sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。

注意:只有kv类型RDD有该操作,单值的没有

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

功能说明:用于将RDD中的元素序列化成对象,存储到文件中。

foreach()遍历RDD中每个元素
函数签名:def foreach(f:T=>Unit):Unit

代码实现:

object action11_foreach {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3具体业务逻辑
        //3.1 创建第一个RDD
        // val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))

        //3.2 收集后打印
        rdd.collect().foreach(println)

        println("****************")

        //3.3 分布式打印
        rdd.foreach(println)

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

四.RDD序列化

在实际开发中我们往往需要自己定义一些对于RDD的操作,那么此时需要注意的是,初始化工作是在Driver端进行的,而实际运行程序是在Executor端进行的,这就涉及到了跨进程通信,是需要序列化的。下面我们看几个例子:
在这里插入图片描述

1,闭包检查

代码演示:

object serializable01_object {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.创建两个对象
        val user1 = new User()
        user1.name = "zhangsan"

        val user2 = new User()
        user2.name = "lisi"

        val userRDD1: RDD[User] = sc.makeRDD(List(user1, user2))

        //3.1 打印,ERROR报java.io.NotSerializableException
        //userRDD1.foreach(user => println(user.name))
        

        //3.2 打印,RIGHT (因为没有传对象到Executor端)
        val userRDD2: RDD[User] = sc.makeRDD(List())
        //userRDD2.foreach(user => println(user.name))

        //3.3 打印,ERROR Task not serializable 
//注意:此段代码没执行就报错了,因为spark自带闭包检查
        userRDD2.foreach(user => println(user.name+" love "+user1.name))

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

//case class User() {
//    var name: String = _
//}
class User extends Serializable {
    var name: String = _
}

2,序列化方法和属性

Driver:算子以外的代码都是在Driver端执行
Executor:算子里面的代码都是在Executor端执行

代码实现:

object serializable02_function {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3.创建一个RDD
        val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark", "hive", "atguigu"))

        //3.1创建一个Search对象
        val search = new Search("hello")

        // Driver:算子以外的代码都是在Driver端执行
        // Executor:算子里面的代码都是在Executor端执行
        //3.2 函数传递,打印:ERROR Task not serializable
        search.getMatch1(rdd).collect().foreach(println)

        //3.3 属性传递,打印:ERROR Task not serializable
        search.getMatche2(rdd).collect().foreach(println)

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

class Search(query:String) extends Serializable {

    def isMatch(s: String): Boolean = {
        s.contains(query)
    }

    // 函数序列化案例
    def getMatch1 (rdd: RDD[String]): RDD[String] = {
        //rdd.filter(this.isMatch)
        rdd.filter(isMatch)
    }

    // 属性序列化案例
    def getMatche2(rdd: RDD[String]): RDD[String] = {
        //rdd.filter(x => x.contains(this.query))
        rdd.filter(x => x.contains(query))
        //val q = query
        //rdd.filter(x => x.contains(q))
    }
}

3,序列化框架——Kryo

参考地址: https://github.com/EsotericSoftware/kryo
Java的序列化能够序列化任何的类。但是比较重,序列化后对象的体积也比较大。
Spark出于性能的考虑,Spark2.0开始支持另外一种Kryo序列化机制。Kryo速度是Serializable的10倍。当RDD在Shuffle数据的时候,简单数据类型、数组和字符串类型已经在Spark内部使用Kryo来序列化。
注意:即使使用Kryo序列化,也要继承Serializable接口或者使用样例类。

object serializable03_Kryo {

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

        val conf: SparkConf = new SparkConf()
                .setAppName("SerDemo")
                .setMaster("local[*]")
                // 替换默认的序列化机制
                .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
                // 注册需要使用kryo序列化的自定义类
                .registerKryoClasses(Array(classOf[Searche]))

        val sc = new SparkContext(conf)

        val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello atguigu", "atguigu", "hahah"), 2)

        val searche = new Searche("hello")
        val result: RDD[String] = searche.getMatchedRDD1(rdd)

        result.collect.foreach(println)
    }
}

class Searche(val query: String) extends Serializable{

    def isMatch(s: String) = {
        s.contains(query)
    }

    def getMatchedRDD1(rdd: RDD[String]) = {
        rdd.filter(isMatch) 
    }

    def getMatchedRDD2(rdd: RDD[String]) = {
        rdd.filter(_.contains(this.query))
    }
}

五.RDD依赖关系

1,查看血缘关系

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

血缘关系图解:
在这里插入图片描述

2,查看依赖关系

依赖关系图解
在这里插入图片描述

六.Stage任务划分

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

(1)Application:初始化一个SparkContext即生成一个Application;
(2)Job:一个Action算子就会生成一个Job;
(3)Stage:Stage等于宽依赖的个数加1;
(4)Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。
注意:Application->Job->Stage->Task每一层都是1对n的关系。

代码实现:

object Lineage03 {
  def main(args: Array[String]): Unit = {
    //TODO 1 创建SparkConf配置文件,并设置App名称
    val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    //TODO 2 利用SparkConf创建sc对象
    //Application:初始化一个SparkContext即生成一个Application
    val sc = new SparkContext(conf)

    //textFile,flatMap,map算子全部是窄依赖,不会增加stage阶段
    val lineRDD: RDD[String] = sc.textFile("D:\\IdeaProjects\\SparkCoreTest\\input\\1.txt")
    val flatMapRDD: RDD[String] = lineRDD.flatMap(_.split(" "))
    val mapRDD: RDD[(String, Int)] = flatMapRDD.map((_, 1))

    //reduceByKey算子会有宽依赖,stage阶段加1,2个stage
    val resultRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_ + _)

    //Job:一个Action算子就会生成一个Job,2个Job
    //job0打印到控制台
    resultRDD.collect().foreach(println)
    //job1输出到磁盘
    resultRDD.saveAsTextFile("D:\\IdeaProjects\\SparkCoreTest\\out")

    //阻塞线程,方便进入localhost:4040查看
    Thread.sleep(Long.MaxValue)

    //TODO 3 关闭资源
    sc.stop()
  }
}

查看http://localhost:4040/jobs/,发现Job有两个。

查看Job0的Stage。由于只有1个Shuffle阶段,所以Stage个数为2。

查看Job1的Stage。由于只有1个Shuffle阶段,所以Stage个数为2。

查看Job0的Stage0的Task个数,2个

查看Job0的Stage1的Task个数,2个

查看Job1的Stage2的Task个数,0个(2个跳过skipped)

查看Job1的Stage3的Task个数,2个

七.RDD持久化

1,RDD Cache缓存

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

代码实现:

object cache01 {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        //3. 创建一个RDD,读取指定位置文件:hello atguigu atguigu
        val lineRdd: RDD[String] = sc.textFile("input1")

        //3.1.业务逻辑
        val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))

        val wordToOneRdd: RDD[(String, Int)] = wordRdd.map {
            word => {
                println("************")
                (word, 1)
            }
        }

        //3.5 cache缓存前打印血缘关系
        println(wordToOneRdd.toDebugString)

        //3.4 数据缓存。
//cache底层调用的就是persist方法,缓存级别默认用的是MEMORY_ONLY
        wordToOneRdd.cache()

        //3.6 persist方法可以更改存储级别
        // wordToOneRdd.persist(StorageLevel.MEMORY_AND_DISK_2)

        //3.2 触发执行逻辑
        wordToOneRdd.collect().foreach(println)
        
        //3.5 cache缓存后打印血缘关系
//cache操作会增加血缘关系,不改变原有的血缘关系
        println(wordToOneRdd.toDebugString)

        println("==================================")
        
        //3.3 再次触发执行逻辑
        wordToOneRdd.collect().foreach(println)

        Thread.sleep(1000000)

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

2,RDD CheckPoint检查点

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

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

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

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

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

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

代码实现:

object checkpoint01 {

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

        //1.创建SparkConf并设置App名称
        val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")

        //2.创建SparkContext,该对象是提交Spark App的入口
        val sc: SparkContext = new SparkContext(conf)

        // 需要设置路径,否则抛异常:Checkpoint directory has not been set in the SparkContext
        sc.setCheckpointDir("./checkpoint1")

        //3. 创建一个RDD,读取指定位置文件:hello atguigu atguigu
        val lineRdd: RDD[String] = sc.textFile("input1")

        //3.1.业务逻辑
        val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))

        val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
            word => {
                (word, System.currentTimeMillis())
            }
        }

        //3.5 增加缓存,避免再重新跑一个job做checkpoint
//        wordToOneRdd.cache()

        //3.4 数据检查点:针对wordToOneRdd做检查点计算
        wordToOneRdd.checkpoint()

        //3.2 触发执行逻辑
        wordToOneRdd.collect().foreach(println)
        // 会立即启动一个新的job来专门的做checkpoint运算

        //3.3 再次触发执行逻辑
        wordToOneRdd.collect().foreach(println)
        wordToOneRdd.collect().foreach(println)

        Thread.sleep(10000000)

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

缓存和检查点的区别

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

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

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

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

posted @ 2022-01-14 10:48  小笼包想飞  阅读(132)  评论(0编辑  收藏  举报