大数据之Spark 核心编程概述完整使用(第五章)

大数据之Spark 核心编程概

Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于
处理不同的应用场景。三大数据结构分别是:
➢ RDD : 弹性分布式数据集
➢ 累加器:分布式共享只写变量
➢ 广播变量:分布式共享只读变量
接下来我们一起看看这三大数据结构是如何在数据处理中使用的。

一、RDD

1、什么是 RDD

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据
处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行
计算的集合。

➢ 弹性
⚫ 存储的弹性:内存与磁盘的自动切换;
⚫ 容错的弹性:数据丢失可以自动恢复;
⚫ 计算的弹性:计算出错重试机制;
⚫ 分片的弹性:可根据需要重新分片。

➢ 分布式:数据存储在大数据集群不同节点上
➢ 数据集:RDD 封装了计算逻辑,并不保存数据
➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现
➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在新的 RDD 里面封装计算逻辑
➢ 可分区、并行计算

2、核心属性

在这里插入图片描述

➢ 分区列表
RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
在这里插入图片描述

➢ 分区计算函数
Spark 在计算时,是使用分区函数对每一个分区进行计算
在这里插入图片描述

➢ RDD 之间的依赖关系
RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建
立依赖关系
在这里插入图片描述

➢ 分区器(可选)
当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区
在这里插入图片描述

➢ 首选位置(可选)
计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
在这里插入图片描述

3、执行原理

从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。
执行时,需要将计算资源和计算模型进行协调和整合。

Spark 框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的
计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计
算。最后得到计算结果。

RDD 是 Spark 框架中用于数据处理的核心模型,接下来我们看看,在 Yarn 环境中,RDD
的工作原理:

1) 启动 Yarn 集群环境

在这里插入图片描述

2) Spark 通过申请资源创建调度节点和计算节点

在这里插入图片描述

3) Spark 框架根据需求将计算逻辑根据分区划分成不同的任务

在这里插入图片描述

4) 调度节点将任务根据计算节点状态发送到对应的计算节点进行计算

在这里插入图片描述

从以上流程可以看出 RDD 在整个流程中主要用于将逻辑进行封装,并生成 Task 发送给
Executor 节点执行计算,接下来我们就一起看看 Spark 框架中 RDD 是具体是如何进行数据
处理的。

二、RDD 创建

Spark 创建的RDD方式为四种

1、RDD创建

1) 从集合(内存)中创建 RDD

从集合中创建 RDD,Spark 主要提供了两个方法:parallelize 和 makeRDD

package com.spack.bigdata.core.rdd.build

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

/**
 * 从集合中创建 RDD,Spark 主要提供了两个方法:parallelize 和 makeRDD
 */
object Spark01_RDD_Memory {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    // 从内存中创建RDD,将内存中的集合的数据作为处理的数据源
    val req = Seq[Int](1, 2, 3, 4)

    // parallelize:  并行
    // val rdd: RDD[Int] = context.parallelize(req)

    //makeRDD方法在底层实现时、其实就是调用了rdd对象的parallelize方法
    val rdd: RDD[Int] = context.makeRDD(req)
    rdd.collect().foreach(println)

    //TODO 关闭环境
    context.stop()
  }
}

package com.spack.bigdata.core.rdd.build

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


/**
 * RDD从内存中读取文件
 */
object Spark02_RDD_File {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    // 从文件中创建RDD,将文件中的数据作为处理的数据源
    //path路径默认以当前环境的根路径为基础,可以写绝对路径,也可以写相对路径
    //    val rdd: RDD[String] = context.textFile("datas/1.txt")

    //path 路径可以是文件的具体路径,也可以目录名称
    //    val rdd: RDD[String] = context.textFile("datas")

    //path 路径可以使用通配符
    //    val rdd: RDD[String] = context.textFile("datas/1*.txt")

    //path 路径还可以是分布式存储系统路径: HDFS
    val rdd: RDD[String] = context.textFile("hdfs://linux1:8082/test.txt")
    rdd.collect().foreach(println)

    //TODO 关闭环境
    context.stop()
  }
}

从底层代码实现来讲,makeRDD 方法其实就是 parallelize 方法

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

2) 从外部存储(文件)创建 RDD

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

package com.spack.bigdata.core.rdd.build

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


/**
 * RDD从内存中读取文件
 * 以文件为单位读取数据
 */
object Spark02_RDD_File1 {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    // 从文件中创建RDD,将文件中的数据作为处理的数据源

    //TextFile 以行为单位来读取数据\ 读取地数据都是字符串
    //wholeTextFiles 以文件为单位读取数据
    //读取的结果表示为元组 第一个元素表示文件的路径 第二个元素表示文件的内容
    val rdd = context.wholeTextFiles("datas")
    rdd.collect().foreach(println)

    //TODO 关闭环境
    context.stop()
  }
}

package com.spack.bigdata.core.rdd.build

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


/**
 * 文件数据源 ---分区设定
 * RDD从内存中读取文件
 * 以文件为单位读取数据
 */
object Spark02_RDD_File_Par {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    //textFile 可以将文件作为数据处理的数据源,默认也可以设定分区
    //minPartitions 最小分区数量
    //math.min(defaultParallelism, 2)
    //    val rdd = context.textFile("datas/1.txt")
    val rdd = context.textFile("datas/1.txt", 2)
    //如果不想使用默认分区数量,可以通过第二个参数指定分区数
    //Spark 读取文件,底层其实使用的就是Hadoop的读取方式
    //分区数量的计算
    //totalSize = 7
    //goalSize =7 /2  = 3 (byte)

    // 7/3 = 2...1 (1.1) +1 =3 (分区)


    rdd.saveAsTextFile("output")
    //TODO 关闭环境
    context.stop()
  }
}

package com.spack.bigdata.core.rdd.build

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


/**
 * 文件数据源 ---分区设定
 * RDD从内存中读取文件
 * 以文件为单位读取数据
 */
object Spark02_RDD_File_Par1 {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    //1、数据以行为单位进行读取
    //Spark 读取文件 采用的是Hadoop的方式读取 所以一行一行读取 和字节数没有关系
    //2、数据读取时、以偏移量为单位、偏移量不会被重复读取
    /**
     * 1@@ =>012
     * 2@@ => 345
     * 3 =>6
     */

    /**
     * 3、数据分区的偏移量范围计算
     * 0 => [0,3] => 12
     * 1 => [3,6] = 3
     * 2 => [6,7] =>
     */
    val rdd: RDD[String] = context.textFile("datas/1.txt", 2)

    rdd.saveAsTextFile("output")
    //TODO 关闭环境
    context.stop()
  }
}

package com.spack.bigdata.core.rdd.build

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


/**
 * 文件数据源 ---分区设定
 * 分区数据的分配
 * 案例分析
 *
 */
object Spark03_RDD_File_Par2 {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    //1、数据以行为单位进行读取

    //14byte /2 = 7byte
    //14 /7 = 2(分区)

    /**
     * 1234567@@ =》 012345678
     * 89@@ =》 9101112
     * 0 =》13
     *
     * 【0,7】 =》 1234567
     * 【7,14】 =》 980
     */


    //如果数据源为多个文件、那吗计算分区时以文件为单位进行分区
    val rdd: RDD[String] = context.textFile("datas/word.txt", 2)


    rdd.saveAsTextFile("output")
    //TODO 关闭环境
    context.stop()
  }
}

3) 从其他 RDD 创建

主要是通过一个 RDD 运算完后,再产生新的 RDD。

4) 直接创建 RDD(new)

使用 new 的方式直接构造 RDD,一般由 Spark 框架自身使用。

2、RDD 并行度与分区

默认情况下,Spark 可以将一个作业切分多个任务后,发送给 Executor 节点并行计算,而能 够并行计算的任务数量我们称之为并行度。这个数量可以在构建 RDD 时指定。记住,这里
的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。

package com.spack.bigdata.core.rdd.build

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

/**
 * RDD分区设定并行度
 */
object Spark01_RDD_Memory_Par {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    conf.set("spark.default.parallelism","5")
    val context = new SparkContext(conf)

    //TODO 创建RDD
    // RDD的并行度 & 分区
    //MakeRDD方法可以传递第二个参数 这个参数可以表示分区数量
    //第二个参数可以不传递的  那吗makeRDD方法会使用默认值: defaultParallelism (默认并行度)
//    val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4), 2)
    // scheduler.conf.getInt("spark.default.parallelism", totalCores)
    //Spark在默认情况下,从配置对象中获取配置参数 spark.default.parallelism
    //如果获取不到,那吗使用totalCores属性 这个属性取值当前环境最大可用核数
    val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4))

      rdd.saveAsTextFile("output")
    //TODO 关闭环境
    context.stop()
  }
}

package com.spack.bigdata.core.rdd.build

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

/**
 * RDD分区设定并行度
 */
object Spark01_RDD_Memory_Par1 {
  def main(args: Array[String]): Unit = {

    //TODO 准备环境 ---
    // [*] 表示当前本机核数是多少------不写[*] 就是单核、设置RDD为应用名称
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    conf.set("spark.default.parallelism", "5")
    val context = new SparkContext(conf)

    //TODO 创建RDD

    //[1,2] [3,4]
    val rdd: RDD[Int] = context.makeRDD(List(1, 2, 3, 4), 2)
    rdd.saveAsTextFile("output")
    //TODO 关闭环境
    context.stop()
  }
}

实例:

val sparkConf =
 new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val dataRDD: RDD[Int] =
 sparkContext.makeRDD(
 List(1,2,3,4),
 4)
val fileRDD: RDD[String] =
 sparkContext.textFile(
 "input",
 2)
fileRDD.collect().foreach(println)
sparkContext.stop()

⚫ 读取内存数据时,数据可以按照并行度的设定进行数据的分区操作,数据分区规则的
Spark 核心源码如下:

def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
 (0 until numSlices).iterator.map { i =>
 val start = ((i * length) / numSlices).toInt
 val end = (((i + 1) * length) / numSlices).toInt
 (start, end)
 }
  }

⚫ 读取文件数据时,数据是按照 Hadoop 文件读取的规则进行切片分区,而切片规则和数
据读取的规则有些差异,具体 Spark 核心源码如下

public InputSplit[] getSplits(JobConf job, int numSplits)
 throws IOException {
 long totalSize = 0; // compute total size
 for (FileStatus file: files) { // check we have valid files
 if (file.isDirectory()) {
 throw new IOException("Not a file: "+ file.getPath());
 }
 totalSize += file.getLen();
 }
 long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
 long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
 FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
 
 ...
 
 for (FileStatus file: files) {
 
 ...
 
 if (isSplitable(fs, path)) {
 long blockSize = file.getBlockSize();
 long splitSize = computeSplitSize(goalSize, minSize, blockSize);
 ...
 }
 protected long computeSplitSize(long goalSize, long minSize,
 long blockSize) {
 return Math.max(minSize, Math.min(goalSize, blockSize));
 }

3、RDD 转换算子

RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value
类型

Value 类型

1) map

➢ 函数签名
def map[U: ClassTag](f: T => U): RDD[U] ➢ 函数说明
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 map
 * 小功能读取日志中间的地址
 *
 */
object Spark01_RDD_Operator_Test {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val value = sc.textFile("datas/apache.log")

    val mapRdd: RDD[String] = value.map(
    //将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换
      line => {
        val spValue = line.split(" ")
        spValue(6)
      }
    )
    mapRdd.collect().foreach(println)
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 map
 *
 */
object Spark01_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    //1,2,3,4
    //2,4,6,8

    def mapFunction(num: Int): Int = {
      num * 2
    }

    //    val mapRdd: RDD[Int] = rdd.map(mapFunction)

    //简写
    //    val mapRdd: RDD[Int] = rdd.map((num: Int) => {num * 2})
    //    val mapRdd: RDD[Int] = rdd.map((num: Int) => num * 2)

    //简写
    //    val mapRdd: RDD[Int] = rdd.map((num) => num * 2)
    //    val mapRdd: RDD[Int] = rdd.map(num => num * 2)

    val mapRdd: RDD[Int] = rdd.map(_ * 2)

    mapRdd.collect().foreach(println)
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 map
 * 并行计算效果演示
 *
 */
object Spark01_RDD_Operator_Transform_Par {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map

    //1、rdd的计算一个分区内的数据是一个一个执行逻辑
    // 只有前面一个数据全部的逻辑执行完毕后、才会执行下一个数据
    //分区内数据的执行是有序的
    //2、不同分区数据计算是无序的

    val rdd = sc.makeRDD(List(1, 2, 3, 4),2)

    val mapRDD = rdd.map(
      num => {
        println(">>>>>>>=" + num)
        num
      }
    )

    val mapRDD1 = mapRDD.map(
      num => {
        println("#########=" + num)
        num
      }
    )

    mapRDD1.collect()
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 *
 * 理解分区不变的含义
 *
 */
object Spark01_RDD_Operator_Transform_Part {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map

    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    //[1,2] [3,4]
    rdd.saveAsTextFile("output")


    val mapRdd: RDD[Int] = rdd.map(_ * 2)
    //[2,4] [6,8]
    mapRdd.saveAsTextFile("output1")

    sc.stop()
  }
}

2) mapPartitions

➢ 函数签名
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]

➢ 函数说明

将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处
理,哪怕是过滤数据。

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * RDD 转换算子 mapPartitions
 *
 */
object Spark02_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)

    //mapPartitions:  是以分区为单位进行数据转换操作
    //但是为将整个分区的数据加载到内存进行引用
    //如果处理完的数据是不会被释放掉、存在对象的引用
    //在内存较小,数据量较大的场合下、容易出现内存溢出
    val mpRDD = rdd.mapPartitions(
      iter => {
        println(">>>>>>>>>>>>>>>")
        //把迭代器里面的数据* 2
        iter.map(_ * 2)
      }
    )
    mpRDD.collect().foreach(println)
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * RDD 转换算子 mapPartitions
 * 小功能练习 -取出每个分区的最大值
 *
 */
object Spark02_RDD_Operator_Transform_Test {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)

    val mapRDD = rdd.mapPartitions(
      iter => {
        //转成集合在调用迭代器
        List(iter.max).iterator
      }
    )
    mapRDD.collect().foreach(println)
  }
}

思考一个问题:map 和 mapPartitions 的区别?

➢ 数据处理角度
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子
是以分区为单位进行批处理操作。

➢ 功能的角度
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。
MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,
所以可以增加或减少数据

➢ 性能的角度
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处
理,所以性能较高。但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能
不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。

3) mapPartitionsWithIndex

➢ 函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
➢ 函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处
理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * RDD 转换算子 mapPartitionsWithIndex
 * 小功能:获取第二个数据分区的数据
 *
 */
object Spark03_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    val mpRDD = rdd.mapPartitionsWithIndex(
      (index, iter) => {
           //获取第二个分区的数据
        if (index == 1) {
          iter
        } else {
          Nil.iterator
        }
      }
    )
    mpRDD.collect().foreach(println)
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * RDD 转换算子 mapPartitionsWithIndex
 * 获取第二个分区数据
 *
 */
object Spark03_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 转换算子Map
    val rdd = sc.makeRDD(List(1, 2, 3, 4))

    val mpRDD = rdd.mapPartitionsWithIndex(
      (index, iter) => {
        iter.map(
          num => {
            (index, num)
          }
        )
      }
    )


    mpRDD.collect().foreach(println)
    sc.stop()
  }
}

4) flatMap

➢ 函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
➢ 函数说明
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射、

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子FlatMap
 */
object Spark04_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 算子 -mapPartitions
    val rdd: RDD[List[Int]] = sc.makeRDD(List(
      List(1, 2), List(3, 4)
    ))

    //这个是集合中的集合、
    val flatRdd: RDD[Int] = rdd.flatMap(
      list => {
        list
      }
    )

    //输出
    flatRdd.collect().foreach(println)

  }
}

结果:
1
2
3
4

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * RDD算子 扁平化
 * 对比在于这个数据是一个集合类型的字符串、使用切割
 */
object Spark04_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 算子 -mapPartitions
    val rdd: RDD[String] = sc.makeRDD(List(
      "Hello WOrd", "Hello Spark"
    ))

    //对比在于这个数据是一个集合类型的字符串、使用切割
    val flatRdd: RDD[String] = rdd.flatMap(
      list => {
        list.split(" ")
      }
    )
    flatRdd.collect().foreach(println)
  }
}

结果:

Hello
WOrd
Hello
Spark

flatMap-小练习

package com.spack.bigdata.core.rdd.operator.transform
import org.apache.spark.{SparkConf, SparkContext}

/**
 * flatMap
 * 小练习
 */
object Spark04_RDD_Operator_Transform2 {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //TODO 算子 -FlatMap
    val rdd = sc.makeRDD(List(List(1, 2), 3,(List(4, 5))))

    val  flatRdd= rdd.flatMap(
     //match 模式匹配
      data => {
        data match {
          //如果是一个集合就返回一个集合
          case list: List[_] => list
            //如果不是集合就给你变成集合
          case dat => List(dat)
        }
      }
    )
    flatRdd.collect().foreach(println)
  }
}

5) glom

➢ 函数签名
def glom(): RDD[Array[T]]
➢ 函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * TODO 转换算子 -glom
 *
 */

object Spark05_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 转换算子 -glom -将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)

    //将一个分区的数据当成了一个数组
    val glomRDD: RDD[Array[Int]] = rdd.glom()
     println(glomRDD)

    //[1,2]  [3,4]
    //[2]     [4]
    // [6]

    val maxRdd = glomRDD.map(
      //取最大值
      array => {
        array.max
      }
    )
    println(maxRdd.collect().sum)
  }
}

6) groupBy

➢ 函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
➢ 函数说明
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样
的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

package com.spack.bigdata.core.rdd.operator.transform

import java.text.SimpleDateFormat

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

/**
 * GroupBy
 * 小练习
 *
 */
object Spark06_RDD_Operator_Test {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 算子 -GroupBy
    val rdd = sc.textFile("datas/apache.log")

//求出 (小时,每一次)
    val timeRdd: RDD[(String, Iterable[(String, Int)])] = rdd.map(
      line => {
        val datas = line.split(" ")
        val tim = datas(3)

        val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
        val date = sdf.parse(tim)

        val sdf1 = new SimpleDateFormat("HH")
        val time = sdf1.format(date)
        (time, 1)

      }
    ).groupBy(_._1)

//汇总 (小时,每一次.size)
    timeRdd.map {
      case (hour, iter) => {
        (hour, iter.size)
      }
    }.collect().foreach(println)

  }
}

package com.spack.bigdata.core.rdd.operator.transform


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

/**
 * 转换算子GroupBy
 */
object Spark06_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 算子 -GroupBy
    val mapRdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)

    //groupBy会将数据源中的每一个数据进行分组判断,根据返回的分组Key进行分组
    //相同的key值的数据会放置在一个组中
    def groupFunction(num: Int): Int = {
      num % 2
    }

    val groupRdd: RDD[(Int, Iterable[Int])] = mapRdd.groupBy(groupFunction)
    groupRdd.collect().foreach(println)
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子GroupBy
 */
object Spark06_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 算子 -GroupBy
    val mapRdd: RDD[String] = sc.makeRDD(List("Hello", "Scala", "Hadoop", "Spark"), 2)

    //获取第一个字符串坐标
    val groupRdd = mapRdd.groupBy(_.charAt(0))
    groupRdd.collect().foreach(println)
    sc.stop()
  }
}

结果:

(H,CompactBuffer(Hello, Hadoop))
(S,CompactBuffer(Scala, Spark))

7) filter

➢ 函数签名
def filter(f: T => Boolean): RDD[T]
➢ 函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出
现数据倾斜。

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 -Filter
 */
object Spark07_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 算子 -Filter
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val numFilter: RDD[Int] = rdd.filter(num => num % 2 != 0)
    numFilter.collect().foreach(println)
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 -Filter
 */
object Spark07_RDD_Operator_Transform_Test {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO 算子 -Filter
    val rdd = sc.textFile("datas/apache.log")
    rdd.filter(
      line => {
        val datas = line.split(" ")
        val str = datas(3)
      str.startsWith("17/05/2015")
      }
    ).collect().foreach(println)

  }
}

8) sample

➢ 函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
➢ 函数说明
根据指定的规则从数据集中抽取数据

package com.spack.bigdata.core.rdd.operator.transform
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 转换算子 -sample
 */
object Spark08_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
    //sample 算子需要传递三个参数
    //1、第一参数表示,抽取数据后是否将数据返回true(返回),false (丢弃)
    //2、第二个参数表示,
                //如果抽取不放回的场合:数据源中每条数据被抽取的概念,基准值概念、
                //如果抽取放回的场合,表示数据源中的每条数据被抽取的可能次数

    //3、第三个参数表示,抽取数据时随机算法的种子
    //如果不传递第三个参数,那吗使用的是当前系统时间

    //    println(rdd.sample(
    //      false,
    //      0.4,
    //      //      1
    //    ).collect().mkString(",")
    //    )
    println(rdd.sample(
      true,
      2,
      //      1
    ).collect().mkString(",")
    )
  }
}

9)distinct

➢ 函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
➢ 函数说明
将数据集中重复的数据去重

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 -distinct
 * 将数据集中重复的数据去重
 */
object Spark09_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4))

    //reduceByKey 相同的Key V来做聚合
    // map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)

    //(1,null),(2,null),(3,null),(4,null),(1,null),(2,null),(3,null),(4,null),
    //(1,null),(1,null),(1,null)
    //(null,null) =>null
    //(1,null) =>1

    val rdd1: RDD[Int] = rdd.distinct()
    rdd1.collect().foreach(println)
  }
}

10) coalesce

➢ 函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]

➢ 函数说明
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少
分区的个数,减小任务调度成本

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子 -coalesce
 * 根据数据量缩减分区,用于大数据集过滤后、提高小数据集的执行效率
 */
object Spark10_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3)

    //coalesce方法默认情况下不会将分区的数据打乱重新组合
    //这种情况下的缩减分区可能导致数据不均衡,出现数据倾斜
    //如果想让数据均衡,可以进行shuffle处理
    //val newRdd = rdd.coalesce(2)

    val newRdd = rdd.coalesce(2, true)
    newRdd.saveAsTextFile("output")
  }
}

11) repartition

➢ 函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

➢ 函数说明
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的
RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition
操作都可以完成,因为无论如何都会经 shuffle 过程。

package com.spack.bigdata.core.rdd.operator.transform
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 转换算子 -repartion
 */
object Spark11_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)
    val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2)

    //coalesce算子可以扩大分区的、但是如果不进行shuffle 操作,是没有意义的,不起作用
    //所以如果想要实现扩大分区效果,需要使用shuffle操作
    //spark提供了一个简化操作
    //缩减分区: coalesce,如果想要数据均衡,可以采用shuffle
    //扩大分区: repartition

    val newRdd = rdd.repartition(3)
    newRdd.saveAsTextFile("output")
  }
}

12) sortBy

➢ 函数签名
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]

➢ 函数说明
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理
的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一
致。中间存在 shuffle 的过程

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 算子 --sortBy
 *
 */
object Spark12_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    val rdd = sc.makeRDD(List(6, 2, 1, 4, 3, 4), 2)

    //TODO 算子 --sortBy
    val sortRdd = rdd.sortBy(num => num)
    sortRdd.saveAsTextFile("output")
  }
}

package com.spack.bigdata.core.rdd.operator.transform
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 算子 --sortBy
 *
 */
object Spark12_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)
    //对比的时候 11  -- 还是1 小 排在 2前面
    val rdd = sc.makeRDD(List(("1", 1), ("11", 2), ("2", 3)), 2)

    //TODO 算子 --sortBy
//    val newRdd = rdd.sortBy(t => t._1)

    //sortBy方法可以根据指定的规则对数据源中的数据进行排序,默认为升序,第二个参数可以改变排序的方式
    //sortBy 默认情况下,不会改变分区、但是中间存在shuffle操作
    val newRdd = rdd.sortBy(t => t._1.toInt)
    newRdd.collect().foreach(println)

  }
}

13) intersection

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

➢ 函数说明
对源 RDD 和参数 RDD 求交集后返回一个新的 RDD

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子
 * 交集-并集-差集-拉链
 */
object Spark13_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    val rdd1 = sc.makeRDD(List(1, 2, 3, 4))
    val rdd2 = sc.makeRDD(List(3, 4, 5, 6))
    val rdd7 = sc.makeRDD(List("3", "4", "5", "6"))

    //交集
    val rdd3 = rdd1.intersection(rdd2)
    println(rdd3.collect().mkString(","))

    //并集:【1,2,3,4,3,4,5,6】
    val rdd4 = rdd1.union(rdd2)
    println(rdd4.collect().mkString(","))

    //差集【1,2】
    val rdd5: RDD[Int] = rdd1.subtract(rdd2)
    println(rdd5.collect().mkString(","))

    //拉链[(1,3),(2,4),(3,5),(4,6)]
    val rdd6: RDD[(Int, Int)] = rdd1.zip(rdd2)
    println(rdd6.collect().mkString(","))
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 交集-并集-差集-拉链
 * 注意事项
 */
object Spark13_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO -算子 -双Value
    //两个数据源要求分区数量要保持一致

    //两个数据源要求分区中数据数量保持一致
    val rdd1 = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2)
    val rdd2 = sc.makeRDD(List(3, 4, 5, 6), 2)

    //拉链[(1,3),(2,4),(3,5),(4,6)]
    val rdd6: RDD[(Int, Int)] = rdd1.zip(rdd2)
    println(rdd6.collect().mkString(","))
  }
}

14) partitionBy

➢ 函数签名
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
➢ 函数说明
将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 交集-并集-差集-拉链
 * 注意事项
 */
object Spark14_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO -算子 -(Key,Value类型)
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    val mapRdd = rdd.map((_, 1))

    //RDD =>PairRDDFunctions
    //隐士转换(二次编译)
    //partitionBy 根据指定的分区规则对数据进行重分区
    mapRdd.partitionBy(new HashPartitioner(2))
      .saveAsTextFile("output")
  }
}

15) reduceByKey

➢ 函数签名
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]

➢ 函数说明
可以将数据按照相同的 Key 对 Value 进行聚合

package com.spack.bigdata.core.rdd.operator.transform

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

/**
 * 转换算子
 * reduceByKey
 */
object Spark15_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO -算子 -(Key,Value类型)
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4)))


    /**
     * x=1  y=2
     * x=3  y=3
     * (a,6)
     * (b,4)
     */
    val reduceRdd: RDD[(String, Int)] = rdd.reduceByKey((x: Int, y: Int) => {
      println(s"x=$x  y=$y")
      x + y
    })

    reduceRdd.collect().foreach(println)
  }
}

16) groupByKey

➢ 函数签名
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]

➢ 函数说明
将数据源的数据根据 key 对 value 进行分组

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子
 * groupByKey
 */
object Spark16_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //TODO groupByKey 将数据源中的数据,相同的Key的数据分在一个组中、形成一个对偶元组
    //元组中的第一个元素就是Key
    //元组中的第二个元素就是相同的Key的Value集合
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4)))
    val groupRdd: RDD[(String, Iterable[Int])] = rdd.groupByKey()
    groupRdd.collect().foreach(println)

    val scalaRdd: RDD[(String, Iterable[(String, Iterable[Int])])] = groupRdd.groupBy(_._1)
    scalaRdd.collect().foreach(println)
  }
}

从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey
可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的
数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较
高。

从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚
合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那
么还是只能使用 groupByKey

17) aggregateByKey

➢ 函数签名
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]

➢ 函数说明
将数据根据不同的规则进行分区内计算和分区间计算

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子
 * aggregateByKey
 *
 * 分区间和分区内时独立开的
 */
object Spark17_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //aggregateByKey存在函数柯里化,有两个参数列表
    //第一个参数列表
    //主要用于碰见第一个key的时候,和value进行区分内计算
    //第二个参数列表需要传递两个参数
    //第一个参数表示分区内计算规则
    //第二个参数表示区间计算规则


    //(a,[1,2]),(a,[3,4])分区内
    //(a,2),(a,4) 分区间
    //(a,6)

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)),2)

    rdd.aggregateByKey(0)(
      (x, y) => math.max(x, y),
      (x, y) => x + y
    ).collect().foreach(println)
    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子
 * aggregateByKey
 *
 * 分区间和分区内时独立开的--
 */
object Spark17_RDD_Operator_Transform1 {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //aggregateByKey存在函数柯里化,有两个参数列表
    //第一个参数列表
    //主要用于碰见第一个key的时候,和value进行区分内计算
    //第二个参数列表需要传递两个参数
    //第一个参数表示分区内计算规则
    //第二个参数表示区间计算规则


    //(a,[1,2]),(a,[3,4])分区内
    //(a,2),(a,4) 分区间
    //(a,6)

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6)), 2)

    rdd.aggregateByKey(0)(
      (x, y) => math.max(x, y),
      (x, y) => x + y
    ).collect().foreach(println)


    sc.stop()
  }
}

18) foldByKey

def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]

➢ 函数说明
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 转换算子
 * foldByKey
 *
 * 当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
 */
object Spark17_RDD_Operator_Transform2 {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //(a,[1,2]),(a,[3,4])分区内
    //(a,2),(a,4) 分区间
    //(a,6)

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(
      ("a", 1), ("a", 2), ("b", 3),
      ("b", 4), ("b", 5), ("a", 6)),
      2)

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

    //简化
    rdd.foldByKey(0)(_+_).collect().foreach(println)
    sc.stop()
  }
}

19) combineByKey

➢ 函数签名
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]

➢ 函数说明
最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于
aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
小练习:将数据 List((“a”, 88), (“b”, 95), (“a”, 91), (“b”, 93), (“a”, 95), (“b”, 98))求每个 key 的平
均值

val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), 
("a", 95), ("b", 98))
val input: RDD[(String, Int)] = sc.makeRDD(list, 2)
val combineRdd: RDD[(String, (Int, Int))] = input.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)
)

20) sortByKey

➢ 函数签名
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)]
➢ 函数说明
在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(true)
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(false)

21) join

➢ 函数签名
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]

➢ 函数说明
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的
(K,(V,W))的 RDD

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * join 转换算子
 */
object Spark21_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    val rdd = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)

    ))


    val rdd1 = sc.makeRDD(List(
      ("a", 4), ("b", 5), ("c", 6)
    ))

    //join :两个不同数据源的数据,相同key的value会连接在一起,形成元组
    // 如果两个数据源中key没有匹配上,那吗数据不会出现结果中
    // 如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔积、数据量会几何性增长、会导致性能降低
    val joinRDD: RDD[(String, (Int, Int))] = rdd.join(rdd1)

    joinRDD.collect().foreach(println)
  }
}

22)leftOuterJoin和rightOuterJoin

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * join 转换算子
 */
object Spark22_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    val rdd = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)

    ))


    val rdd1 = sc.makeRDD(List(
      ("a", 4), ("b", 5),// ("c", 6)
    ))


//    val joinRDD: RDD[(String, (Int, Option[Int]))] = rdd.leftOuterJoin(rdd1)
   val joinRDD: RDD[(String, (Option[Int], Int))] = rdd.rightOuterJoin(rdd1)

    joinRDD.collect().foreach(println)
  }
}

23) cogroup

➢ 函数签名
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
➢ 函数说明
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD

package com.spack.bigdata.core.rdd.operator.transform

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


/**
 * 转换算子
 * cogroup 分组,链接
 */
object Spark23_RDD_Operator_Transform {
  def main(args: Array[String]): Unit = {

    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    val rdd = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)

    ))

    val rdd1 = sc.makeRDD(List(
      ("a", 4), ("b", 5), ("c", 6), ("c", 7)
    ))

    // cogroup: connect +group(分组,链接)
    val cogroupRDD: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd.cogroup(rdd1)
    cogroupRDD.collect().foreach(println)

  }
}

24)案例实操

package com.spack.bigdata.core.rdd.operator.transform

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

/**
 * 1) 数据准备
     agent.log:时间戳,省份,城市,用户,广告,中间字段使用空格分隔。
   2) 需求描述
     统计出每一个省份每个广告被点击数量排行的 Top3
   3) 需求分析
   4) 功能实现
 */
object Spark24_RDD_Req {
  def main(args: Array[String]): Unit = {
    val sparkconf = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(sparkconf)

    //1、获取原始数据:时间戳,省份,城市、用户、广告
    val dataRdd: RDD[String] = sc.textFile("datas/agent.log")

    //2、将原始数据进行结构的转换。方便统计
    //时间戳,省份,城市,用户,广告
    // =>
    // ((省份,广告),1)

    val mapRdd = dataRdd.map(
      line => {
        val datas = line.split(" ")
        ((datas(1), datas(4)), 1)
      }
    )

    //3、将转换结构后的数据,进行分组聚合
    //((省份,广告),1) => ((省份,广告),sum)
    val reduceByKeyRdd: RDD[((String, String), Int)] = mapRdd.reduceByKey(_ + _)

    //    //4、将聚合的结果进行结构转换
    //    ((省份,广告),sum) => ( 省份,( 广告 , sum ))
    val newMapRdd = reduceByKeyRdd.map {
      case ((prv, ad), sum) => {
        (prv, (ad, sum))
      }
    }


//    //5、将转换结构后的数据根据省份进行分组
//    //( 省份 ,【(广告A,sumA ),(广告B,sumB )】)
    val groupRDD: RDD[(String, Iterable[(String, Int)])] = newMapRdd.groupByKey()

    //6、将分组后的数据组内排序(降序),取前三名\mapValues key保持不变、只对V操作的时候采用这个
    val resultRdd = groupRDD.mapValues(
      iter => {
        iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(3)
      }
    )
//
//
//    //7、采集数据打印控制台
//
    resultRdd.collect().foreach(println)
  }

}

三、RDD 行动算子

1) reduce

➢ 函数签名
def reduce(f: (T, T) => T): T
➢ 函数说明
聚集 RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据

package com.spack.bigdata.core.rdd.operator.action

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

/**
 * 行动算子
 */
object Spark02_RDD_Operator_Action {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)
    val rdd = sc.makeRDD(List(1, 2, 3, 4))

    //TODO 1、行动算子 -聚集RDD中的所有元素,现居何分区内的数据、在聚合分区间的数据
    val i: Int = rdd.reduce(_ + _)
    println(i)

    //collect: 方法将不同分区的数据按照分区顺序采集到Driver端内存中、形成数组
    val ints: Array[Int] = rdd.collect()
    val str = ints.mkString(",")
    println(str)


    //count: 数据源中数据的个数
    val cnt = rdd.count()
    println(cnt)


    // 获取数据源中的第一次
    val first = rdd.first()
    println(first)


    //take : 获取N个数据
    val ints1: Array[Int] = rdd.take(3)
    println(ints1.mkString(","))


    //takeOrdered 数据先排序后、取N个数据
    val rdd1: RDD[Int] = sc.makeRDD(List(0,4, 3, 2, 1))
    val ints2 = rdd1.takeOrdered(3)
    println(ints2.mkString(","))

    sc.stop()
  }
}

2)aggregate

➢ 函数签名
def aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U

➢ 函数说明
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

package com.spack.bigdata.core.rdd.operator.action

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

/**
 * 行动算子
 */
object Spark03_RDD_Operator_Action {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)
    val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)

    //TODO -行动算子
    //TODO 1、aggregateByKey 初始值只会参与分区内计算
    //TODO 2、aggregate 初始值会参与分区内计算,并且和参与分区间计算

    //10 + 13 +17 =40
    val i: Int = rdd.aggregate(10)(_ + _, _ + _)
    println(i)

    println("----------------------------------")
    //➢ 函数说明
    //折叠操作,aggregate 的简化版操作
    val result: Int = rdd.fold(10)(_ + _)
    println(result)

    sc.stop()
  }
}

3) countByKey

➢ 函数签名
def countByKey(): Map[K, Long]

➢ 函数说明
统计每种 key 的个数

package com.spack.bigdata.core.rdd.operator.action

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

/**
 * 行动算子
 * countByKey 求出A的次数
 * countByValue  求出每一个值出现的次数
 */
object Spark04_RDD_Operator_Action {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)
    //TODO -行动算子
    val rdd = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3)))

    //求出A的次数
    val stringToLong: collection.Map[String, Long] = rdd.countByKey()
    println(stringToLong)


    //求出每一个值出现的次数
    val rdd2 = sc.makeRDD(List(1, 2, 3, 4))
    val intToLong = rdd2.countByValue()
    println(intToLong)
    sc.stop()
  }
}

4) save 相关算子

➢ 函数签名
def saveAsTextFile(path: String): Unit
def saveAsObjectFile(path: String): Unit
def saveAsSequenceFile(
path: String,
codec: Option[Class[_ <: CompressionCodec]] = None): Unit
➢ 函数说明
将数据保存到不同格式的文件中

package com.spack.bigdata.core.rdd.io

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

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

    val operator = new SparkConf().setMaster("local[*]").setAppName("WordCount")
    val sc = new SparkContext(operator)


    val rdd = sc.makeRDD(List(
      ("a", 1),
      ("b", 2),
      ("c", 3),
    ))
    rdd.saveAsTextFile("output1")
    rdd.saveAsObjectFile("output2")
    rdd.saveAsSequenceFile("output3")

    sc.stop()
  }

}

5) foreach

➢ 函数签名
def foreach(f: T => Unit): Unit = withScope {
val cleanF = sc.clean(f)
sc.runJob(this, (iter: Iterator[T]) => iter.foreach(cleanF))
}➢ 函数说明
分布式遍历 RDD 中的每一个元素,调用指定函数\

val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
// 收集后打印
rdd.map(num=>num).collect().foreach(println)
println("****************")
// 分布式打印
rdd.foreach(println)

四、RDD 序列化

1) 闭包检查

从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor 端执行。那么在 scala 的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就
形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给 Executor
端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列
化,这个操作我们称之为闭包检测。Scala2.12 版本后闭包编译方式发生了改变

2) 序列化方法和属性

从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor 端执行,看如下代码:

package com.spack.bigdata.core.rdd.action

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


/**
 * RDD 序列化 -闭包检测
 */
object Spark08_RDD_Operator_Action {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    //foreach 其实是Driver端内部集合的循环遍历方法
    val rdd = sc.makeRDD(List(1, 2, 3, 4))
    val user = new User();

    //RDD算子中传递的函数是会包含闭包操作、那吗就会进行检测功能
    //闭包要引用外部的变量、外部的变量就应该传递到闭包里面
    rdd.foreach(
      num => {
        println("age= " + (user.age + num))
      }
    )
  }
}

class User extends Serializable {
  val age: Int = 30
}

3) Kryo 序列化框架

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

注意:即使使用 Kryo 序列化,也要继承 Serializable 接口。

package com.spack.bigdata.core.rdd.serial

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

/**
 * RDD实际执行时的问题
 */
object Spark01_RDD_Serial {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

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

    val search = new Search("h")
//    search.getMatch1(rdd).collect().foreach(println)

    search.getMatch2(rdd).collect().foreach(println)

    sc.stop()
  }

  //查询对象
  //类的构造参数其实是类的属性,构造参数需要进行闭包检测,其实就等同于类进行闭包检测
  class Search(query: String) {
    def isMatch(s: String): Boolean = {
      s.contains(this.query)
    }

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

    //属性序列化案例
    def getMatch2(rdd: RDD[String]): RDD[String] = {

      //s明显是方法中的局部变量、所以要改变它的生命周期、形成闭包、所以说s不需要类怎吗怎么办
      var s =query
      rdd.filter(x => x.contains(s))
    }

  }

}

五、RDD 依赖关系

1) RDD 血缘关系

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

package com.spack.bigdata.core.rdd.dep

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

/**
 * 血源关系
 */
object Spark01_RDD_Dep {
  def main(args: Array[String]): Unit = {
    //建立和Spark框架的链接
    val conf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("WordCount")
    val sc = new SparkContext(conf)
    //TODO 执行业务操作
    val lines: RDD[String] = sc.textFile("datas/words.txt")
    //血源关系
    println(lines.toDebugString)
    println("**************************")

    val words: RDD[String] = lines.flatMap(_.split(" "))

    println(words.toDebugString)
    println("**************************")

    val wordOne = words.map(word => (word, 1))
    println(words.toDebugString)
    println("**************************")

    val wordSum = wordOne.reduceByKey(_ + _)
    println(wordSum.toDebugString)
    println("**************************")

    val tuples: Array[(String, Int)] = wordSum.collect()
    tuples.foreach(println)
    sc.stop()

  }
}

2) RDD 依赖关系

这里所谓的依赖关系,其实就是两个相邻 RDD 之间的关系

val sc: SparkContext = new SparkContext(conf)
val fileRDD: RDD[String] = sc.textFile("input/1.txt")
println(fileRDD.dependencies)
println("----------------------")
val wordRDD: RDD[String] = fileRDD.flatMap(_.split(" "))
println(wordRDD.dependencies)
println("----------------------")
val mapRDD: RDD[(String, Int)] = wordRDD.map((_,1))
println(mapRDD.dependencies)
println("----------------------")
val resultRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_+_)
println(resultRDD.dependencies)
resultRDD.collect()

3) RDD 窄依赖

窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用,
窄依赖我们形象的比喻为独生子女。
class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd)

4) RDD 宽依赖

宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会
引起 Shuffle,总结:宽依赖我们形象的比喻为多生。
 @transient private val _rdd: RDD[_ <: Product2[K, V]],
 val partitioner: Partitioner,
 val serializer: Serializer = SparkEnv.get.serializer,
 val keyOrdering: Option[Ordering[K]] = None,
 val aggregator: Option[Aggregator[K, V, C]] = None,
 val mapSideCombine: Boolean = false)
 extends Dependency[Product2[K, V]]

5) RDD 阶段划分

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

在这里插入图片描述

6) RDD 阶段划分源码

try {
 // New stage creation may throw an exception if, for example, jobs are run on 
a
 // HadoopRDD whose underlying HDFS files have been deleted.
 finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
} catch {
 case e: Exception =>
 logWarning("Creating new stage failed due to exception - job: " + jobId, e)
 listener.jobFailed(e)
 return
}
……
private def createResultStage(
 rdd: RDD[_],
 func: (TaskContext, Iterator[_]) => _,
 partitions: Array[Int],
 jobId: Int,
 callSite: CallSite): ResultStage = {
val parents = getOrCreateParentStages(rdd, jobId)
val id = nextStageId.getAndIncrement()
val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite)
stageIdToStage(id) = stage
updateJobIdStageIdMaps(jobId, stage)
stage
}

……
private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] 
= {
getShuffleDependencies(rdd).map { shuffleDep =>
 getOrCreateShuffleMapStage(shuffleDep, firstJobId)
}.toList
}
……
private[scheduler] def getShuffleDependencies(
 rdd: RDD[_]): HashSet[ShuffleDependency[_, _, _]] = {
val parents = new HashSet[ShuffleDependency[_, _, _]]
val visited = new HashSet[RDD[_]]
val waitingForVisit = new Stack[RDD[_]]
waitingForVisit.push(rdd)
while (waitingForVisit.nonEmpty) {
 val toVisit = waitingForVisit.pop()
 if (!visited(toVisit)) {
 visited += toVisit
 toVisit.dependencies.foreach {
 case shuffleDep: ShuffleDependency[_, _, _] =>
 parents += shuffleDep
 case dependency =>
 waitingForVisit.push(dependency.rdd)
 }
 } }
parents
}

7) RDD 任务划分

RDD 任务切分中间分为:Application、Job、Stage 和 Task
⚫ Application:初始化一个 SparkContext 即生成一个 Application;
⚫ Job:一个 Action 算子就会生成一个 Job;
⚫ Stage:Stage 等于宽依赖(ShuffleDependency)的个数加 1;
⚫ Task:一个 Stage 阶段中,最后一个 RDD 的分区个数就是 Task 的个数。

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

在这里插入图片描述

8) RDD 任务划分源码

val tasks: Seq[Task[_]] = try {
 stage match {
 case stage: ShuffleMapStage =>
 partitionsToCompute.map { id =>
 val locs = taskIdToLocations(id)
 val part = stage.rdd.partitions(id)
 new ShuffleMapTask(stage.id, stage.latestInfo.attemptId,
 taskBinary, part, locs, stage.latestInfo.taskMetrics, properties, 
Option(jobId),
 Option(sc.applicationId), sc.applicationAttemptId)
 }
 case stage: ResultStage =>
 partitionsToCompute.map { id =>
 val p: Int = stage.partitions(id)
 val part = stage.rdd.partitions(p)
 val locs = taskIdToLocations(id)
 new ResultTask(stage.id, stage.latestInfo.attemptId,
 taskBinary, part, locs, id, properties, stage.latestInfo.taskMetrics,
 Option(jobId), Option(sc.applicationId), sc.applicationAttemptId)
 }
 }
……
val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
……
override def findMissingPartitions(): Seq[Int] = {
mapOutputTrackerMaster
 .findMissingPartitions(shuffleDep.shuffleId)
 .getOrElse(0 until numPartitions) }

六、RDD 持久化

1) RDD Cache 缓存

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

存储级别:

 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)

在这里插入图片描述

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

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

package com.spack.bigdata.core.rdd.persist

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

/**
 * RDD持久化
 * cache、persist
 *
 * RDD中不存储数据
 * 如果一个RDD需要重复使用,那吗需要从头再次执行来获取数据
 * RDD对象可以重用、但是数据无法重用
 */
object Spark01_RDD_Persist {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Persist")
    val sc = new SparkContext(operator)

    val list = List("Hello List", "Hello Spark")
    val rdd = sc.makeRDD(list)
    val flatRDD = rdd.flatMap(_.split(" "))

    val mapRdd =   flatRDD.map(word => {
      println("*******@@@@@@@@@*********")
      (word, 1)
    })

//    val mapRdd = flatRDD.map((_, 1))


    val reduceRDD: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
    reduceRDD.collect().foreach(println)


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

    val groupRdd = mapRdd.groupByKey()
    groupRdd.collect().foreach(println)

    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.persist

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}

/**
 * RDD持久化
 * cache 内存 、persist
 *
 * RDD对象的持久化操作不一定是为了重用
 * 在数据执行较长、或数据比较重要的场合也可以采用持久化操作
 */
object Spark02_RDD_Persist {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Persist")
    val sc = new SparkContext(operator)

    val list = List("Hello List", "Hello Spark")
    val rdd = sc.makeRDD(list)
    val flatRDD = rdd.flatMap(_.split(" "))

    val mapRdd = flatRDD.map(word => {
      println("*******@@@@@@@@@*********")
      (word, 1)
    })

    //    val mapRdd = flatRDD.map((_, 1))
    //设置缓存 -cache() 默认持久化的操作、只能将数据保存到内存中、如果想要保存到磁盘文件、需要更改存储级别
    //    mapRdd.cache()
    mapRdd.persist(StorageLevel.DISK_ONLY)

    val reduceRDD: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
    reduceRDD.collect().foreach(println)


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

    val groupRdd = mapRdd.groupByKey()
    groupRdd.collect().foreach(println)

    sc.stop()
  }
}

2) RDD CheckPoint 检查点

所谓的检查点其实就是通过将 RDD 中间结果写入磁盘
由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点
之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。

package com.spack.bigdata.core.rdd.persist

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}

/**
 * RDD持久化 ---检查点
 * cache 内存 、persist
 *
 */
object Spark03_RDD_Persist {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Persist")
    val sc = new SparkContext(operator)

    sc.setCheckpointDir("cp")

    val list = List("Hello List", "Hello Spark")
    val rdd = sc.makeRDD(list)
    val flatRDD = rdd.flatMap(_.split(" "))

    val mapRdd = flatRDD.map(word => {
      println("*******@@@@@@@@@*********")
      (word, 1)
    })


    //checkpoint() 需要落盘的,需要指定检查点保存路径
    //检查点路径保存的文件、当作业执行完毕后、不会被删除
    //一般保存路径都是在分布式存储系统: HDFS
    mapRdd.checkpoint()

    val reduceRDD: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
    reduceRDD.collect().foreach(println)


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

    val groupRdd = mapRdd.groupByKey()
    groupRdd.collect().foreach(println)

    sc.stop()
  }
}

package com.spack.bigdata.core.rdd.persist

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

/**
 * RDD持久化 ---检查点
 * 区别
 * cache 内存 、persist
 *
 */
object Spark05_RDD_Persist {
  def main(args: Array[String]): Unit = {

    //cache: 将数据临时存储在内存中进行数据重用
              //会在血源关系
    //persist: 将数据临时存储在磁盘文件中、进行数据重用
        //涉及到磁盘IO,性能较低、但是数据安全
        //如果作业执行完毕、临时保存的数据文件就会丢失

    //Checkpoint:将数据长久的保存在磁盘文件中进行数据重用、涉及到磁盘IO、性能较低、但是数据不安全、
    //  为了保证数据安全、所以一般情况下、会独立执行作业
    //    为了能够提高效率、一般情况下、是需要和cache联合使用
          //checkpoint等同于改变数据源
    val operator = new SparkConf().setMaster("local[*]").setAppName("Persist")
    val sc = new SparkContext(operator)


    sc.setCheckpointDir("sp")

    val list = List("Hello List", "Hello Spark")
    val rdd = sc.makeRDD(list)
    val flatRDD = rdd.flatMap(_.split(" "))

    val mapRdd = flatRDD.map(word => {
      println("*******@@@@@@@@@*********")
      (word, 1)
    })



//    mapRdd.cache()
    mapRdd.checkpoint()
    println(mapRdd.toDebugString)
    val reduceRDD: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
    reduceRDD.collect().foreach(println)
    println("*******************")
    println(mapRdd.toDebugString)

    sc.stop()
  }
}

3) 缓存和检查点区别

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

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

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

七、RDD 分区器

Spark 目前支持 Hash 分区和 Range 分区,和用户自定义分区。Hash 分区为当前的默认
分区。分区器直接决定了 RDD 中分区的个数、RDD 中每条数据经过 Shuffle 后进入哪个分
区,进而决定了 Reduce 的个数。

➢ 只有 Key-Value 类型的 RDD 才有分区器,非 Key-Value 类型的 RDD 分区的值是 None
➢ 每个 RDD 的分区 ID 范围:0 ~ (numPartitions - 1),决定这个值是属于那个分区的。

1) Hash 分区:对于给定的 key,计算其 hashCode,并除以分区个数取余

class HashPartitioner(partitions: Int) extends Partitioner {
 require(partitions >= 0, s"Number of partitions ($partitions) cannot be 
negative.")
 def numPartitions: Int = partitions
 def getPartition(key: Any): Int = key match {
 case null => 0
 case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
 }
 override def equals(other: Any): Boolean = other match {
 case h: HashPartitioner =>
 h.numPartitions == numPartitions
 case _ =>
 false
 }
 override def hashCode: Int = numPartitions
}

2) Range 分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序

class RangePartitioner[K : Ordering : ClassTag, V](
 partitions: Int,
 rdd: RDD[_ <: Product2[K, V]],
 private var ascending: Boolean = true)
 extends Partitioner {
 // We allow partitions = 0, which happens when sorting an empty RDD under the 
default settings.
 require(partitions >= 0, s"Number of partitions cannot be negative but found 
$partitions.")
 private var ordering = implicitly[Ordering[K]]
 // An array of upper bounds for the first (partitions - 1) partitions
 private var rangeBounds: Array[K] = {
 ...
 }
 def numPartitions: Int = rangeBounds.length + 1
 private var binarySearch: ((Array[K], K) => Int) = 
CollectionsUtils.makeBinarySearch[K]
def getPartition(key: Any): Int = {
 val k = key.asInstanceOf[K]
 var partition = 0
 if (rangeBounds.length <= 128) {
 // If we have less than 128 partitions naive search
 while (partition < rangeBounds.length && ordering.gt(k, 
rangeBounds(partition))) {
 partition += 1
 }
 } else {
 // Determine which binary search method to use only once.
 partition = binarySearch(rangeBounds, k)
 // binarySearch either returns the match location or -[insertion point]-1
 if (partition < 0) {
 partition = -partition-1
 }
 if (partition > rangeBounds.length) {
 partition = rangeBounds.length
 }
 }
 if (ascending) {
 partition
 } else {
 rangeBounds.length - partition
 }
 }
 override def equals(other: Any): Boolean = other match {
 ...
 }
 override def hashCode(): Int = {
 ...
 }
 @throws(classOf[IOException])
 private def writeObject(out: ObjectOutputStream): Unit = 
Utils.tryOrIOException {
 ...
 }
 @throws(classOf[IOException])
 private def readObject(in: ObjectInputStream): Unit = Utils.tryOrIOException 
{
 ...
 } }

3)自定义分区

package com.spack.bigdata.core.wc

import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}


/**
 * save 分区保存文件
 */
object Spark07_WordCount {
  def main(args: Array[String]): Unit = {

    val operator = new SparkConf().setMaster("local[*]").setAppName("WordCount")
    val sc = new SparkContext(operator)


    val rdd = sc.makeRDD(List(
      ("nba", "xxxxxxxxx"),
      ("cba", "xxxxxxxxx"),
      ("wnba", "xxxxxxxxx"),
      ("nba", "xxxxxxxxx")
    ), 3)


    val partRdd: RDD[(String, String)] = rdd.partitionBy(new MyPartitioner)
    partRdd.saveAsTextFile("output")
  }


  /**
   * 自定义分区器
   * 1、继承Partitioner
   * 2、重写方法
   */
  class MyPartitioner extends Partitioner{

    //分区数量
    override def numPartitions: Int = 3

    //返回数据的分区索引 (从0开始)
    override def getPartition(key: Any): Int ={

      key match {
        case "nba" =>0;
        case "wnba"=>1;
        case _ =>2;
      }

    }
  }

}



八、RDD 文件读取与保存

Spark 的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。
文件格式分为:text 文件、csv 文件、sequence 文件以及 Object 文件;
文件系统分为:本地文件系统、HDFS、HBASE 以及数据库。

➢ text 文件

// 读取输入文件
val inputRDD: RDD[String] = sc.textFile("input/1.txt")
// 保存数据
inputRDD.saveAsTextFile("output")

➢ sequence 文件

SequenceFile 文件是 Hadoop 用来存储二进制形式的 key-value 对而设计的一种平面文件(Flat 
File)。在 SparkContext 中,可以调用 sequenceFile[keyClass, valueClass](path)
// 保存数据为 SequenceFile
dataRDD.saveAsSequenceFile("output")
// 读取 SequenceFile 文件
sc.sequenceFile[Int,Int]("output").collect().foreach(println)

➢ object 对象文件

对象文件是将对象序列化后保存的文件,采用 Java 的序列化机制。可以通过 objectFile[T: 
ClassTag](path)函数接收一个路径,读取对象文件,返回对应的 RDD,也可以通过调用
saveAsObjectFile()实现对对象文件的输出。因为是序列化所以要指定类型。
// 保存数据
dataRDD.saveAsObjectFile("output")
// 读取数据
sc.objectFile[Int]("output").collect().foreach(println)

九 、累加器

1)实现原理

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

2) 基础编程

不返回的
在这里插入图片描述

package com.spack.bigdata.core.acc

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

object Spark01_Acc {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)

    val rdd = sc.makeRDD(List(1, 2, 3, 4))
//    val i = rdd.reduce(_ + _)
//    println(i)

    var sum = 0

    rdd.foreach(
      num => {
        sum += sum
      }
    )

    println("sum=" + sum)
    sc.stop()
  }
}

返回的
在这里插入图片描述

1、系统累加器系统累加器

package com.spack.bigdata.core.acc

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

/** *
 * 数据累加器
 */
object Spark03_Acc {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)
    val rdd = sc.makeRDD(List(1, 2, 3, 4))

    //获取系统的累加器
    //Spark默认就是提供了简单数据聚合的累加器
    val sumAcc: LongAccumulator = sc.longAccumulator("sum")
    println(sumAcc)


    //map 转换算子
  val mapRdd =   rdd.map(
      num => {
        //使用累加器
        sumAcc.add(num)
        num
      }
    )

    //获取累加器的值
    //少加:转换算子中调用累加器、如果没有行动算子的话,那么不会执行
    //多加:转换算子中调用累加器、如果没有行动算子的话,那么不会执行
    //一般情况下、累加器会放值行动算子进行操作
    mapRdd.collect()
//    mapRdd.collect()
    println(sumAcc.value)
    sc.stop()
  }

}

自定义 数据累加器

package com.spack.bigdata.core.acc

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


import scala.collection.mutable


/** *
 * 自定义数据累加器
 */
object Spark04_Acc_WordCount {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local[*]").setAppName("Operator")
    val sc = new SparkContext(operator)
    val rdd = sc.makeRDD(List("hello", "spark", "hello"))

    //val value = rdd.map((_, 1)).reduceByKey(_ + _)
    sc.longAccumulator
    val wcAcc = new MyAccumulaor()
    sc.register(wcAcc, "wordCountAcc")

    rdd.foreach(
      word => {

        //数据累加(使用累加器)
        wcAcc.add(word)
      }
    )

    println(wcAcc.value)
    sc.stop()
  }

  /**
   * 自定义数据累加器:wordCount
   *
   * 1、继承 AccumulatorV2 定义泛型
   *
   * IN: 累加器输入的数据类型
   * out: 累加器返回的数据类型mutable,Map[String,Long]
   */
  class MyAccumulaor extends AccumulatorV2[String, mutable.Map[String, Long]] {

    private val wcMap: mutable.Map[String, Long] = mutable.Map[String, Long]()


    //判断是否初始胡状态
    override def isZero: Boolean = {
      wcMap.isEmpty
    }

    override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] ={
      new MyAccumulaor()
    }

    //清空
    override def reset(): Unit = {
      wcMap.clear()
    }

    /*
    获取累加器需要计算的值
     */
    override def add(word: String): Unit = {
      //万一没找到给个O、表示不存在、map的旧的值
      val newCnt = wcMap.getOrElse(word, 0L) + 1
      wcMap.update(word, newCnt)
    }

    //Driver合并多个累加器的
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {

      val map1 = this.wcMap
      val map2 = other.value

      map2.foreach {
        case (word, count) => {
          val newCount = map1.getOrElse(word, 0L) + count
          map1.update(word, newCount)
        }
      }

    }


    //获取累加器的结果
    override def value: mutable.Map[String, Long] = {
      wcMap
    }
  }

}

十、广播变量

1)实现原理

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

2) 基础编程

package com.spack.bigdata.core.acc

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

import scala.collection.mutable

/** *
 * 数据累加器
 */
object Spark06_Bc {
  def main(args: Array[String]): Unit = {
    val operator = new SparkConf().setMaster("local").setAppName("Acc")
    val sc = new SparkContext(operator)

    val rdd1 = sc.makeRDD(List(
      ("a", 1), ("b", 2), ("c", 3)
    )
    )

    val map = mutable.Map(("a", 4), ("b", 5), ("c", 6))

    //封装广播变量
    val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(map)

    rdd1.map {
      case (w, c) => {
        //访问网络变量
        val l: Int = bc.value.getOrElse(w, 0)
        (w,(c,l))
      }
    }.collect().foreach(println)


    sc.stop()
  }

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值