Spark Core

Spark Core

第1章 Spark概述

1.1 Spark是什么

Spark 是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。

1.2 Spark and Hadoop

➢ Hadoop

⚫ Hadoop 是由 java 语言编写的,在分布式服务器集群上存储海量数据并运行分布式 分析应用的开源框架

⚫ 作为 Hadoop 分布式文件系统,HDFS 处于 Hadoop 生态圈的最下层,存储着所有 的 数 据 , 支 持 着 Hadoop 的 所 有 服 务 。 它 的 理 论 基 础 源 于 Google 的 TheGoogleFileSystem 这篇论文,它是 GFS 的开源实现。

⚫ MapReduce 是一种编程模型,Hadoop 根据 Google 的 MapReduce 论文将其实现,作为 Hadoop 的分布式计算模型,是 Hadoop 的核心。基于这个框架,分布式并行程序的编写变得异常简单。综合了 HDFS 的分布式存储和 MapReduce 的分布式计算,Hadoop 在处理海量数据时,性能横向扩展变得非常容易。

⚫ HBase 是对 Google 的 Bigtable 的开源实现,但又和 Bigtable 存在许多不同之处。 HBase 是一个基于 HDFS 的分布式数据库,擅长实时地随机读/写超大规模数据集。它也是 Hadoop 非常重要的组件。

➢ Spark

⚫ Spark 是一种由 Scala 语言开发的快速、通用、可扩展的大数据分析引擎

⚫ Spark Core 中提供了 Spark 最基础与最核心的功能

⚫ Spark SQL 是 Spark 用来操作结构化数据的组件。通过 Spark SQL,用户可以使用SQL 或者 Apache Hive 版本的 SQL 方言(HQL)来查询数据。

⚫ Spark Streaming 是 Spark 平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的 API。

由上面的信息可以获知,Spark 出现的时间相对较晚,并且主要功能主要是用于数据计算,所以其实 Spark 一直被认为是 Hadoop 框架的升级版。

1.3 Spark or Hadoop

Hadoop 的 MR 框架和 Spark 框架都是数据处理框架,那么我们在使用时如何选择呢?

⚫ Hadoop MapReduce 由于其设计初衷并不是为了满足循环迭代式数据流处理,因此在多并行运行的数据可复用场景(如:机器学习、图挖掘算法、交互式数据挖掘算法)中存在诸多计算效率等问题。所以 Spark 应运而生,Spark 就是在传统的 MapReduce 计算框架的基础上,利用其计算过程的优化,从而大大加快了数据分析、挖掘的运行和读写速度,并将计算单元缩小到更适合并行计算和重复使用的 RDD 计算模型。

⚫ 机器学习中 ALS、凸优化梯度下降等。这些都需要基于数据集或者数据集的衍生数据反复查询反复操作。MR 这种模式不太合适,即使多 MR 串行处理,性能和时间也是一个问题。数据的共享依赖于磁盘。另外一种是交互式数据挖掘,MR 显然不擅长。而Spark 所基于的 scala 语言恰恰擅长函数的处理。

⚫ Spark 是一个分布式数据快速分析项目。它的核心技术是弹性分布式数据集(Resilient Distributed Datasets),提供了比 MapReduce 丰富的模型,可以快速在内存中对数据集进行多次迭代,来支持复杂的数据挖掘算法和图形计算算法。

Spark 和Hadoop 的根本差异是多个作业之间的数据通信问题 : Spark 多个作业之间数据 通信是基于内存,而 Hadoop 是基于磁盘。

⚫ Spark Task 的启动时间快。Spark 采用 fork 线程的方式,而 Hadoop 采用创建新的进程的方式。

⚫ Spark 只有在 shuffle 的时候将数据写入磁盘,而 Hadoop 中多个 MR 作业之间的数据交互都要依赖于磁盘交互

⚫ Spark 的缓存机制比 HDFS 的缓存机制高效。

经过上面的比较,我们可以看出在绝大多数的数据计算场景中,Spark 确实会比 MapReduce更有优势。但是 Spark 是基于内存的,所以在实际的生产环境中,由于内存的限制,可能会

由于内存资源不够导致 Job 执行失败,此时,MapReduce 其实是一个更好的选择,所以 Spark并不能完全替代 MR。

1.3.1 Hadoop MapReduce缺点:

1、表达能力有限

2、磁盘IO开销大,任务之间的衔接涉及Io开销

3、延迟高,Map任务要全部结束,reduce任务才能开始

Spark借鉴Hadoop MapReduce优点的同时,解决了MapReduce所面临的问题,有如下优点:

1、 Spark的计算模式也属于MapReduce,但不局限于Map和Reduce操作,还提供多种数据集操作类型,编程模型比Hadoop MapReduce更灵活 。

2、Spark提供了内存计算,可将中间结果放到内存中,对于迭代运算效率更高

3、Spark基于DAG的任务调度执行机制,要优于Hadoop MapReduce的迭代执行机制

1.4 Spark 核心模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dk3MaeIO-1610598183342)(C:\Users\85356\AppData\Roaming\Typora\typora-user-images\1610504323772.png)]

Spark Core

Spark Core 中提供了 Spark 最基础与最核心的功能,Spark 其他的功能如:Spark SQL, Spark Streaming,GraphX, MLlib 都是在 Spark Core 的基础上进行扩展的

Spark SQL

Spark SQL 是 Spark 用来操作结构化数据的组件。通过 Spark SQL,用户可以使用 SQL或者 Apache Hive 版本的 SQL 方言(HQL)来查询数据。

Spark Streaming

Spark Streaming 是 Spark 平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的 API。

Spark MLlib

MLlib 是 Spark 提供的一个机器学习算法库。MLlib 不仅提供了模型评估、数据导入等额外的功能,还提供了一些更底层的机器学习原语。

Spark GraphX

GraphX 是 Spark 面向图计算提供的框架与算法库。

第2章 Spark 快速上手

2.1 WordCount

shell模式

查看HDFS上的文件

img

从Linux模式下查看HDFS下文件

img

查看HDFS上指定文件内容

img

  1. 读取文件
sc.textFile("hdfs:///kb10/wordcount.txt")

---如果想指定本地Linux的文件,写法如下:
sc.textFile("file:///root/sotware/test.txt")

img

  1. 分割

    sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" "))
    

img

  1. 对单词计数,转换成(hello,1),(spark,1 )这种形式

    sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1))
    

img

  1. 统计

    sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1))
    .reduceByKey(_+_)
    

img

  1. 输出结果

    sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1))
    .reduceByKey(_+_).collect
    

img

  1. 遍历

    sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1))
    .reduceByKey(_+_).collect.foreach(println)
    

img

IDE

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

object WordCount {
  def main(args: Array[String]): Unit = {
    // 创建Spark运行配置对象
    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    // 创建Spark上下文连接对象
    val sc = new SparkContext(conf)

    // 读取数据
    val fileRDD = sc.textFile("file:///D:\\All_Studying_Resources\\spark\\codes\\sparkdemo\\data\\wordcount\\Hello.txt")

    // 将文件中的数据进行分词
    val word2OneRDD = fileRDD.flatMap(x=>x.split(" "))

    // 转换数据结构  word => (word,1)
    val word2CountRDD = word2OneRDD.map((_,1))

    // 将转换结构后的数据按照相同的单词进行分组聚合
    val wordCountRDD = word2CountRDD.reduceByKey(_+_)

    // 将数据聚合结果采集到内存中
    val word2Count = word2CountRDD.collect()

    // 打印结果
    word2Count.foreach(println)


    sc.stop()

  }
}

执行过程中,会产生大量的执行日志,如果为了能够更好的查看程序的执行结果,可以在项

目的 resources 目录中创建 log4j.properties 文件,并添加日志配置信息:

log4j.rootCategory=ERROR, console

在这里插入图片描述

第3章 Spark运行环境

Spark 作为一个数据处理框架和计算引擎,被设计在所有常见的集群环境中运行, 在国 内工作中主流的环境为 Yarn,不过逐渐容器式环境也慢慢流行起来。接下来,我们就分别看看不同环境下 Spark 的运行
在这里插入图片描述

3.1 Spark安装
  1. 上传安装包

  2. 解压

    cd /opt/download/hadoop
    tar -zxvf spark-2.4.4-bin-hadoop2.6.tgz -C /opt/software/hadoop
    
  3. 配置环境变量

    vi /etc/profile
    export SPARK_HOME=/opt/software/hadoop/spark244
    export PATH=$SPARK_HOME/bin:$PATH
    
    
    激活环境变量
    source /etc/profile
    

4.配置文件

cd $SPARK_HOME
cp ./conf/spark-env.sh.template ./conf/spark-env.sh    // 复制模板文件并修改配置文件
vi ./conf/spark-env.sh

添加以下内容
export JAVA_HOME=/opt/software/jdk180
export HADOOP_HOME=/opt/software/hadoop/hadoop260
##指定Hadoop配置文件目录,这样可以直接使用hadoop的配置文件
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop    

----使用standalone模式必须配置的
#指定 master 的主机
export SPARK_MASTER_HOST=192.168.199.130
#指定 master 的端口
export SPARK_MASTER_PORT=7077

5.启动

在 spark 目录下,输入:

cd /opt/software/hadoop/spark224/sbin
./start-all.sh

查看是否有 Master 与 Worker 进程

img
如果Worker启动不了的时候,做以下操作:

[root@single sbin]# vi spark-config.sh 
添加JDK路径

img

6.启动spark-shell 测试Scala交互式环境

spark-shell --master spark://192.168.199.130:7077

img

可以通过浏览器访问192.168.199.130:8080/

img

测试Spark on YARN

spark-shell --master yarn

img

----单节点可配可不配置项

先把 slaves.template 重命名为 slaves
cp slaves.template slaves
mv slaves.template slaves
vim slaves

指定下worker会运行在哪些节点上
single
......
......

安装问题

  1. web访问不了
    1. 防火墙是否关闭
    2. 主机映射是否配置
    3. 服务是否挂掉
    4. 端口是否冲突
    5. 查看错误日志$SPARK_HOME/logs重启spark

3.2 Spark运行模式

1. 本机   
spark-shell --master local[*]  
local[1]:这里的1指的是1个线程,
*指的是一台机器里的所有线程(也可以直接写spark-shell,相当于spark-shell local[*])
不需要启动spark进程(Worker  Master)

所谓的 Local 模式,就是不需要其他任何节点资源就可以在本地执行 Spark 代码的环境,一般用于教学,调试,演示等,之前在 IDEA 中运行代码的环境我们称之为开发环境,不太一样。


2.standalone    
spark-shell --master spark://192.168.199.130:7077
必须启动Master个Worker
独立部署(Standalone)模式。Spark 的 Standalone 模式体现了经典的 master-slave 模式。


3.YARN
spark-shell --master yarn



退出使用:quit
如果使用ctrl+c,其实进程是没有关闭的


spark shell 相当于一个spark进程,有一个sparksubmit进程,
如果使用ctrl + c,这个sparksubmit进程是不会关闭的

第4章 Spark运行架构

4.1 运行架构

Spark 框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。

如下图所示,它展示了一个 Spark 执行时的基本结构。图形中的 Driver 表示 master,

负责管理整个集群中的作业任务调度。图形中的 Executor 则是 slave,负责实际执行任务。

img

4.2 核心组件

由上图可以看出,对于 Spark 框架有两个核心组件:

4.2.1 Driver

Spark驱动器节点,用于执行Spa扩容任务中的main方法,负责实际代码的执行工作。Driver在Spark作业执行时主要负责:

➢ 将用户程序转化为作业(job)

➢ 在Executor之间调度任务(task)

➢ 跟踪Executor的执行情况

➢ 通过UI展示查询运行情况

Driver:驱动程序,Spark中的Driver即运行上述Application的main函数并创建SparkContext,创建SparkContext的目的是为了准备Spark应用程序的运行环境,在Spark中有SparkContext负责与ClusterManager通信,进行资源申请、任务的分配和监控等,当Executor部分运行完毕后,Driver同时负责将SparkContext关闭。
Driver 不一定运行在Master节点上,Driver 程序运行在哪儿,是根据一个参数指定的,取决于client和cluster,比如说指定是client,那么driver就运行client这台机器上

简单理解,所谓的 Driver 就是驱使整个应用运行起来的程序,也称之为 Driver 类

4.2.2 Executor

Spark Executor是集群中工作节点(Worker)中的一个JVM进程,负责在Spark作业中运行具体任务(Task),任务彼此之间相互独立。Spark应用启动时,Executor节点被同时启动,并且始终伴随着整个Spark应用的生命周期而存在。如果有Executor节点发生了故障或崩溃,Spark应用也可以继续执行,会将出错节点上的任务调度到其他Executor节点上继续运行

Executor 有两个核心功能:

➢ 负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程

➢ 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存数据加速运算。

Executor:是运行在工作节点(WorkerNode)的一个进程,负责运行Task。(执行相关代码)
(每个application获取自己的Executor,每个Task处理一个RDD分区)
(一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整)scala
4.2.3 Master & Worker

Spark 集群的独立部署环境中,不需要依赖其他的资源调度框架,自身就实现了资源调 度的功能,所以环境中还有其他两个核心组件:Master 和 Worker,这里的 Master 是一个进 程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于 Yarn 环境中的 RM, 而 Worker 呢,也是进程,一个 Worker 运行在集群中的一台服务器上,由 Master 分配资源对 数据进行并行的处理和计算,类似于 Yarn 环境中 NM。

4.2.4 ApplicationMaster

Hadoop 用户向 YARN 集群提交应用程序时,提交程序中应该包含 ApplicationMaster,用 于向资源调度器申请执行任务的资源容器 Container,运行用户自己的程序任务 job,监控整 个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。

说的简单点就是,ResourceManager(资源)和 Driver(计算)之间的解耦合靠的就是 ApplicationMaster。

4.3 核心概念
4.3.1 Executor 与 Core

Spark Executor 是集群中运行在工作节点(Worker)中的一个 JVM 进程,是整个集群中的专门用于计算的节点。在提交应用中,可以提供参数指定计算节点的个数,以及对应的资 源。这里的资源一般指的是工作节点 Executor 的内存大小和使用的虚拟 CPU 核(Core)数量。

4.3.2 并行度(Parallelism)

在分布式计算框架中一般都是多个任务同时执行,由于任务分布在不同的计算节点进行计算,所以能够真正地实现多任务并行执行,记住,这里是并行,而不是并发。这里我们将整个集群并行执行任务的数量称之为并行度。那么一个作业到底并行度是多少呢?这个取决于框架的默认配置。应用程序也可以在运行过程中动态修改

4.3.3 有向无环图(DAG)

在这里插入图片描述

这里所谓的有向无环图,并不是真正意义的图形,而是由 Spark 程序直接映射成的数据流的高级抽象模型。简单理解就是将整个程序计算的执行过程用图形表示出来,这样更直观,更便于理解,可以用于表示程序的拓扑结构。

DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。

4.4 提交流程

所谓的提交流程,其实就是我们开发人员根据需求写的应用程序通过 Spark 客户端提交给 Spark 运行环境执行计算的流程。在不同的部署环境中,这个提交过程基本相同,但是又有细微的区别,我们这里不进行详细的比较,但是因为国内工作中,将 Spark 引用部署到Yarn 环境中会更多一些,所以本课程中的提交流程是基于 Yarn 环境的。

下面两张图可以看自己觉得思路清晰的

在这里插入图片描述

这里写图片描述

1、为应用构建基本的运行环境,即为Driver创建一个SparkContext进行资源的申请、任务的分配和监控

2、Clsuter Manager(资源管理器)为Executor分配资源,并启动Executor进程

3、 SparkContext根据RDD的依赖关系构建DAG图,DAG图提交给DAGScheduler解析成Stage,然后把一个个TaskSet提交给底层调度器TaskScheduler处理。
Executor向SparkContext申请Task,TaskScheduler将Task发放给Executor运行并提供应用程序代码 。

4、Task在Executor上运行把执行结果反馈给TaskScheduler,然后反馈给DAGScheduler,运行完毕后写入数据并释放所有资源

Spark运行架构特点:

  1. 每个Application都有自己专属的Executor进程,并且该进程在Application运行期间一直驻留。Executor进程以多线程的方式运行Task。
  2. Spark运行过程与资源管理器无关,只要能够获取Executor进程并保存通信即可。
  3. Task采用数据本地性和推测执行等优化机制。

总结:三种角色

Spark有本地运行模式,stand alone模式,集群模式,yarn模式,mesos模式等多种模式。这些模式的主要组成部分都可以看成SparkContext,Cluster Manager,Executor三个部分,其中SparkContext负责管理Application的执行,与ClusterManager通信,进行资源的申请,任务的调度,监控。Cluster Manager负责管理集群资源,Executor负责执行task。
在不同的模式下,ClusterManager由不同的组件担任,在本地,stand alone和集群模式下,cluster manager是master,在Yarn 模式中由Resource Manager担任,在Mesos模式中由Application Master担任。

第5章 Spark 核心编程

Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于 处理不同的应用场景。三大数据结构分别是:

➢ RDD : 弹性分布式数据集

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

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

5.1 RDD
5.1.1 什么是RDD

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

简单的解释:

RDD是将数据拆分成多个分区的集合,存储在集群的工作节点上的内存和磁盘中,并执行正确的操作

个人理解:RDD是个集合,有多个分区,数据存储在Worker上的内存和磁盘中

➢ 弹性

⚫ 存储的弹性:内存与磁盘的自动切换;

⚫ 容错的弹性:数据丢失可以自动恢复;

⚫ 计算的弹性:计算出错重试机制;

⚫ 分片的弹性:可根据需要重新分片。

➢ 分布式:数据存储在大数据集群不同节点上

➢ 数据集:RDD 封装了计算逻辑,并不保存数据

➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现

➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在 新的 RDD 里面封装计算逻辑

➢ 可分区、并行计算

RDD默认存放在内存中,当内存不足,Spark自动将RDD写入磁盘
容错性

5.1.2 核心属性 RDD五大特性(高频面试点)

➢ A list of partitions 分区列表(一系列的分区(分片)信息,一个task处理一个分区(同为stage下))

RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。

RDD由很多partition构成,在spark中,计算式,有多少partition就对应有多少个task执行
!!一个Job可以划分很多个stage,每个stage按照算子指定内容划分partition,一个RDD可以理解为一个分区列表

➢ A function for computing each split 分区计算函数(每个分区上都有compute函数,计算该分区中数据)

Spark 在计算时,是使用分区函数对每一个分区进行计算

➢ A list of dependencies on other RDDs(RDD之间有一系列依赖)

RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系

➢ Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned(分区器决定数据(key-value)分配)至哪个分区

当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区

➢ Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file) 首选位置

(优先位置列表,将计算任务分派到其所在数据块的存储位置,体现了移动数据不如移动计算)

最优的位置取计算,也就是数据的本地性
比如说数据如果存在DataNode1上,那么就在当前的节点上计算,不用数据传输
---------------              --------------   
|  DataNode1  |             |   DataNode2  |
|             |             |              |
|  Worker     |             |	Worker     |
|             |             |              |  
 -------------               --------------

5.1.3 基础编程
5.1.4.1 RDD 创建

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

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

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

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

object Demo01_CreateRDD extends App {
  val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)
  println("------第一种:集合创建RDD-----")
  // parallelize创建RDD
  println("---parallelize创建RDD-----")
  val rdd1: RDD[Int] = sc.parallelize(List(1,2,3,4))

  // rdd1.collect().foreach(x=>print(s"$x "))
  println(rdd1.collect().mkString(" "))
  println("rdd1的分区数是:"+rdd1.partitions.size)
  println()


  println("-----makeRDD创建--------")
  val rdd2: RDD[Int] = sc.makeRDD(List(1,2,3,4))


  rdd2.collect().foreach(x=>print(s"$x "))
  println()
  println("rdd2的分区数是:"+rdd2.partitions.size)



  println("-----第二种:外部文件创建RDD------")
  val fileRDD: RDD[String] = sc.textFile("file:///D:\\All_Studying_Resources\\spark\\codes\\sparkdemo\\data\\wordcount\\Hello.txt")
  val str: String = fileRDD.collect().mkString(" ")
  str.foreach(print)
    
    prinln("---第三种:从其他RDD创建-----")
	val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    private val rdd2: RDD[Int] = rdd1.map(x => {
    x * 2
  })
    
    
    println("-----直接创建(new)-----")
  sc.stop()
}

在这里插入图片描述

5.1.4.2 RDD 并行度与分区

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

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

object NumSlices extends App {

  private val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)

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

  val fileRDD: RDD[String] = sc.textFile("file:///D:\\All_Studying_Resources\\spark\\codes\\sparkdemo\\data\\wordcount\\Hello.txt",4)

  val str: String = fileRDD.flatMap(x => x.split(" "))
    .map((_, 1))
    .reduceByKey(_ + _)
    .collect()
    .mkString(" ")
  str

  println("-------numSlieces---------")
  str.foreach(x=>print(s"$x"))


}

在这里插入图片描述

5.1.4.3 RDD 转换算子

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

Value 类型

1)

map

➢ 函数签名

def map[U: ClassTag](f: T => U): RDD[U]

➢ 函数说明

将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换

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

object RDDTransformation extends App {

  val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)

  println("----map------")
  val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
/*  private val rdd2: RDD[Int] = rdd1.map(x => {
    x * 2
  })*/

  rdd1.map(x=>{
    x*2
  }).collect().mkString("").foreach(x=>print(s"$x "))

  println()

  rdd1.map(x=>{
    ""+ x
  }).collect().mkString(" ").foreach(x=>print(s"$x "))

  println()


  val rdd2: RDD[(Int, Int)] = rdd1.map(x=>(x,1))

     array直接打印是地址,转成字符可以看到值
  println(rdd2.collect().mkString("-"))

    sc.stop()
}

在这里插入图片描述
在这里插入图片描述

2)

mapPartitions

➢ 函数签名

def mapPartitions[U: ClassTag](

f: Iterator[T] => Iterator[U],

preservesPartitioning: Boolean = false): RDD[U]

➢ 函数说明

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

package RDDTRansformation

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

object MapPartition extends App {


  val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)


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

  val rdd2: RDD[Int] = rdd1.map(_*2)

  val rdd3: RDD[Int] = rdd2.mapPartitions(x=>x.map(_*2))

  println(rdd3.collect().mkString(" "))

  // map和MapPartition的区别:map会把里面的每一个元素拿出来进行处理
  // MapPartitions 是对每一个分区做处理local[4] 默认4个分区,x获取到的是一个分区的数据

  // 一次计算一个分区的数据,不用频繁去做计算  

}

在这里插入图片描述

思考一个问题: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 RDDTRansformation

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

object MapPartitionsWithIndex extends App {
  private val conf: SparkConf = new SparkConf()
    .setMaster("local[5]")
    .setAppName("MapPartitionWithIndex")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)

  // 元素是1-10 划分成5个分区,默认平均分
  val rdd1: RDD[Int] = sc.parallelize(1 to 10)

  // List(1,2)
  val rdd2: RDD[(Int, Int)] = rdd1.mapPartitionsWithIndex((index, items)=>(items.map(x=>(index,x))))

  println(rdd2.collect().mkString("-"))

  // 后面的x是值,前面的是index是分区索引编号

  sc.stop()
}

在这里插入图片描述

4)

flatMap

➢ 函数签名

def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]

➢ 函数说明

将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射

package RDDTRansformation

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

object FlatMap extends App {
  val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)


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

  val rdd2: RDD[Int] = rdd1.flatMap(x=>x)

  println(rdd2.collect().mkString(" "))

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

  val fileRDD: RDD[String] = sc.textFile("file:///D:\\All_Studying_Resources\\spark\\codes\\sparkdemo\\data\\wordcount\\Hello.txt")

  private val str: Any = fileRDD.flatMap(x=>x.split(" ")).collect().mkString(" ")

  println(str)


  println("----------Map&&Flatten---------")

  val a: Array[String] = fileRDD.map(x=>x.split(" ")).collect().flatten

  a.foreach(x=>print(s"$x "))

  sc.stop()



}

在这里插入图片描述

5)

glom

➢ 函数签名

def glom(): RDD[Array[T]]

➢ 函数说明

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

scala> sc.makeRDD(Array(1,2,3,4))
res0: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at makeRDD at <console>:25

scala> res0.collect()
res1: Array[Int] = Array(1, 2, 3, 4)

scala> res0.glom()
res2: org.apache.spark.rdd.RDD[Array[Int]] = MapPartitionsRDD[1] at glom at <console>:26

scala> res0.glom().collect
res3: Array[Array[Int]] = Array(Array(1, 2), Array(3, 4))

在这里插入图片描述

6)

groupBy

➢ 函数签名

def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]

➢ 函数说明

将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

package RDDTRansformation

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

object GroupBy extends App {
  val conf: SparkConf = new SparkConf()
    .setMaster("local[*]")
    .setAppName(this.getClass.getName)

  val sc: SparkContext = SparkContext.getOrCreate(conf)

  private val a: RDD[String] = sc.parallelize(List("dog","cat","pig","slamon"))

  // 转换成(k,v)形式
  private val b: RDD[(Int, String)] = a.map(x=>(x.length,x))
  println(b.collect().mkString(" "))

  private val groupByKey: String = b.groupByKey().collect().mkString(" ")
  println(groupByKey)

}

在这里插入图片描述

7)

filter

➢ 函数签名

def filter(f: T => Boolean): RDD[T]

➢ 函数说明

将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。

package RDDTRansformation

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

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

    val conf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc = SparkContext.getOrCreate(conf)

    val rdd1 = sc.makeRDD(1 to 5)

    val rdd2 = rdd1.filter(_%2==0)

    println(rdd2.collect().mkString(" "))
  }
}

在这里插入图片描述

8)

sample

➢ 函数签名

def sample(

withReplacement: Boolean,

fraction: Double,

seed: Long = Utils.random.nextLong): RDD[T]

➢ 函数说明

根据指定的规则从数据集中抽取数据

第一个参数表示抽出的数据是否放回(true/false),第二个参数表示抽的数据,第三个参数表示随机种子

package RDDTRansformation

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

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

    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)


    val rdd1 = sc.parallelize(1 to 10)

    // true代表的是比如这个从1-10里抽的是1,会把1再放回去,下次可能还是1,可以重复

    val rdd2 = rdd1.sample(true,0.4,2)

    println(rdd2.collect().mkString(" "))
    
    sc.stop()

  }
}

在这里插入图片描述

9)

distinct

➢ 函数签名

def distinct()(implicit ord: Ordering[T] = null): RDD[T]

def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

➢ 函数说明

将数据集中重复的数据去重

package RDDTRansformation

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

object Distinct {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)


    val rdd1 = sc.parallelize(1 to 5)

    val rdd2 = sc.parallelize(3 to 8)

    val union = rdd1.union(rdd2).collect().mkString(" ")

    println(union)


    println(union.distinct.mkString(" "))



  }
}

在这里插入图片描述

10)

coalesce

➢ 函数签名

def coalesce(numPartitions: Int, shuffle: Boolean = false,

partitionCoalescer: Option[PartitionCoalescer] = Option.empty)

(implicit ord: Ordering[T] = null)
RDD[T]

➢ 函数说明

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

分区的个数,减小任务调度成本

coalesce(numPartitions) :将 RDD 中的分区数量减少到 numpartition。

package RDDTRansformation

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

object Coalesce {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(1 to 10,4)

    val rdd2 = rdd1.coalesce(6)


    println(rdd2.partitions.size)     // 4
    // 什么这里还是4呢,因为没有Shuffle,
    // 没法增加分区,只能减少分区,减少相当于把多个分区合并了一下

    val rdd3 = rdd1.coalesce(2)

    println(rdd3.partitions.size)

  }
}

在这里插入图片描述

11)

repartition

➢ 函数签名

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

➢ 函数说明

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

(增加、减少分区都可以完成)

package RDDTRansformation

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

object Repartition {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)
    
    val rdd1 = sc.parallelize(1 to 10,3)

    val rdd2 = rdd1.repartition(6)

    
    println(rdd2.partitions.size)


  }
}

在这里插入图片描述

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 的过程

根据 key 进行排序,默认为升序

package RDDTRansformation

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

object SortBy {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)


    val rdd1 = sc.parallelize(List(10,2,3,5,13,6,3))

    println(rdd1.sortBy(x => x).collect().mkString(" "))


    println(rdd1.sortBy(x => x * (-1)).collect().mkString(" "))

  }
}

在这里插入图片描述

Value 类型

13)

intersection (交集)

➢ 函数签名

def intersection(other: RDD[T]): RDD[T]

➢ 函数说明

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

package RDDTRansformation

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

object Intersection {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(1 to 5)


    val rdd2 = sc.parallelize(3 to 8)


    val rdd3 = rdd1.intersection(rdd2)

    println(rdd3.collect().mkString(" "))


println("------cartesian:做笛卡尔积------")
  // 生成的是个PairRDD
  private val cartesianRDD: RDD[(Int, Int)] = rdd1.cartesian(rdd2)
  println(cartesianRDD.collect().mkString(" "))
      
  }
}

在这里插入图片描述

14)

union

➢ 函数签名

def union(other: RDD[T]): RDD[T]

➢ 函数说明

对源 RDD 和参数 RDD 求并集后返回一个新的 RDD

package RDDTRansformation

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

object Union {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(Array(1,2,3))

    val rdd2 = sc.parallelize(4 to 6)

    println(rdd1.union(rdd2).collect().mkString(" "))


    println("-----当两个RDD数据类型不同时------")
    val rdd3 = sc.parallelize(Array("a","b","c"))

    val str = rdd1.toString().union(rdd3.toString())

    str.foreach(print)



  }
}

在这里插入图片描述

15)

subtract

➢ 函数签名

def subtract(other: RDD[T]): RDD[T]

➢ 函数说明

以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集

package RDDTRansformation

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

object Substarct {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(1 to 5)
    val rdd2 = sc.parallelize(3 to 8)

    val rdd3 = rdd1.subtract(rdd2)

    println(rdd3.collect().mkString(" "))

  }
}

在这里插入图片描述

16)

zip

➢ 函数签名

def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]

➢ 函数说明

将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。

package RDDTRansformation

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

object Zip {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(List(1,2,3,4))

    val rdd2 = sc.parallelize(List(3,4,5,6))

    println(rdd1.zip(rdd2).collect().mkString(" "))

  }
}

在这里插入图片描述

Key - Value 类型

17)

partitionBy

➢ 函数签名

def partitionBy(partitioner: Partitioner): RDD[(K, V)]

➢ 函数说明

将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner

package RDDTRansformation

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

object PartitionBy {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)

    val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

    // 进行partitionBy操作,对PairRDD进行分区操作
    // 如果原有的partitionRDD和现有的partitionRDD 是一致就不进行分区
    // 否则会生成shuffleRDD,即产生shuffle过程

    // 常用的分区器HashPartitioner   RangePartitioner

    val rdd2 = rdd1.partitionBy(new org.apache.spark.HashPartitioner(2))

    println(rdd1.partitions.size)
    println(rdd2.partitions.size)

  }
}


在这里插入图片描述

18)

reduceByKey

➢ 函数签名

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

def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]

➢ 函数说明

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

package RDDTRansformation

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

object ReduceByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)


    val rdd1 = sc.parallelize(List("dog","cat","pig","slamon"))

    // 转换成(x.length,x)的形式
    val rdd2 = rdd1.map(x=>(x.length,x))

    // 对相同的k做聚合
    val rdd3 = rdd2.reduceByKey(_+_)

    println(rdd3.collect().mkString(" "))


  }
}

在这里插入图片描述

19)

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 RDDTRansformation

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

object GroupByKey {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]")
      .setAppName(this.getClass.getName)

    val sc: SparkContext = SparkContext.getOrCreate(conf)


    val rdd1 = sc.parallelize(List("dog","cat","pig","slamon"))

    // 转换成(x.length,x)的形式
    val rdd2 = rdd1.map(x=>(x.length,x))


    println(rdd2.groupByKey().collect().mkString(" "))


  }
}

在这里插入图片描述

思考一个问题:reduceByKey 和 groupByKey 的区别?

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

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

mapValues

原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素,**仅适用于PairRDD

package transformation

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

object MapValues extends App {

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

  private val a: RDD[String] = sc.parallelize(List("dog","cat","panda"))

  // 将普通RDD转换成PairRDD
 // private val pairRDD: RDD[(Int, String)] = a.map(x=>(x.length,x))
   // _表示占位符的意思
    private val d: RDD[(Int, String)] = pairRDD.mapValues(_+"hhhh")
  println(pairRDD.collect().mkString(" "))

  private val d: RDD[(Int, String)] = pairRDD.mapValues(x=>(x+"hhhh"))

  println(d.collect().mkString(" "))


}


// mapValues对PairRDD的value进行处理,key保持不变

在这里插入图片描述

action 算子
take

返回一个包含数据集前 n 个元素的数

​ count:计算元素数量

​ first:返回RDD第一个元素

​ reduce:根据指定函数,对RDD中的元素进行两两计算,返回计算结果

package action

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

object Count extends App {
  private val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("union")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)

  // map:对前一个RDD里面传过来的数据,每一个元素一个操作
  private val a: RDD[Int] = sc.parallelize(1 to 10)

  private val b: RDD[Int] = a.map(_*2)

  println("-------count--------")
  // count是统计元素数量
  println(a.count())

  // take:返回前n个元素
  println("------take------")
  println(a.collect().mkString(" "))
  private val arr2: String = a.collect().mkString(" ").take(3)
  println(arr2)

  println("-----first:返回RDD第一个元素----")
  println(a.first())

  println("-------reduce:对所有元素做聚合操作-------")
  private val res: Int = a.reduce((x, y)=>x+y)
  println(res)
  println(a.reduce(_ + _))



    
    
    sc.stop()
}


在这里插入图片描述

lookup

用于PAirRDD,返回K对应的所有V值,求出想应key所有value

package action

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

object LookUp extends App {
  private val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("union")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)

  private val lookupRDD: RDD[(Char, Int)] = sc.parallelize(List(('a',1),('b',3),('a',2),('c',4)))

  println(lookupRDD.lookup('a'))

}

在这里插入图片描述

最值

返回最大值 最小值

package action

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

object Max extends App {
  private val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("union")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)
  

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

  println(rdd1.max())
  println(rdd1.min())

sc.stop()

}

在这里插入图片描述

saveAsTextFile(path)

将数据集的元素作为文本文件(或文本文件集)写 入本地文件系统、HDFS或任何其他 hadoop 支持 的文件系统的给定目录中。Spark 将对每个元素调 用 toString,将其转换为文件中的一行文本。

package action

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

object SaveAsText extends App {
  private val conf: SparkConf = new SparkConf().setMaster("local[6]").setAppName("union")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)

  private val a: RDD[Int] = sc.parallelize(1 to 100,10)

    // setMaster改成local[10]的情况下
  a.saveAsTextFile("file:///D:\\notepad学习笔记\\spark\\codes\\spark_day0104\\src\\data\\saveAsText10")


}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

collect

以Array返回RDD的所有元素,一般在过滤或者处理足够晓得结果时使用不会改变结果,只是封装成一个Array做输出

foreach:对元素做遍历

package action

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

object Collect extends App {

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

  private val rdd1: RDD[Int] = sc.parallelize(List(1,2,3,4,5,6))
  val rdd2 = rdd1.collect()

  private val rdd3: String = rdd2.mkString(" ")
  println(rdd3)

  rdd3.foreach(x=>print(s"$x "))



}


在这里插入图片描述

注意:setMaster:local[x]指定的是线程资源数,并不是分区数,numSlices这种才是指定分区数
join

把相同key的value放到一起 ,形如 (K,(V,W))

package test0106

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

object JoinDemo extends App {

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


  // 创建两个PairRDD
  private val rdd: RDD[(Int, String)] = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

  private val rdd1: RDD[(Int, Int)] = sc.parallelize(Array((1,4),(2,5),(3,6)))



  // 进行join操作,在类型为(K,V)和(K,W)的RDD上调用
  // 返回一个相同的key对应的所有元素堆在一起的(K,(V,W))的RDD
  println("-----join:相同key,value放到一起--------")
  private val rdd3: RDD[(Int, (String, Int))] = rdd.join(rdd1)
  println(rdd3.collect().mkString(" "))


  // setMaster:local[x]指定的是线程资源数,并不是分区数,numSlices这种才是指定分区数


  println("------------cogroup-----------")
  // 针对的也是两个PairRDD
  // cogroup返回结果是两个迭代器
  private val rdd4: RDD[(Int, (Iterable[String], Iterable[Int]))] = rdd.cogroup(rdd1)
  println(rdd4.collect().mkString(" "))

  println("---当存在多个键值重复的情况----")
  private val rdd5: RDD[(Int, Int)] = sc.parallelize(Array((1,4),(1,5),(3,6)))
  private val rdd6: RDD[(Int, (Iterable[String], Iterable[Int]))] = rdd.cogroup(rdd5)
  println(rdd6.collect().mkString(" "))

  sc.stop()

}


在这里插入图片描述

partitionBy

重新进行分区

package test0106

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

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

object PartitionByDemo extends App {

  private val conf: SparkConf = new SparkConf().setMaster("local[6]").setAppName("partitionByTest")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)


  // 创建两个PairRDD
  private val rdd: RDD[(Int, String)] = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

  println(rdd.partitions.size)

  // 进行partitionBy操作,对PairRDD进行分区操作
  // 如果原有的partitionRDD和现有的partitionRDD 是一致就不进行分区
  // 否则会生成shuffleRDD,即产生shuffle过程

  // 常用的分区器HashPartitioner   RangePartitioner
  private val rdd2: RDD[(Int, String)] = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))

  println(rdd2.partitions.size)


  sc.stop()



}



  sc.stop()



}

在这里插入图片描述

zip

拉链用法,两个RDD一一对应,组成成(K,V)形式的RDD

package test0106

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

object Zip extends App {
  private val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("joinTest")
  private val sc: SparkContext = SparkContext.getOrCreate(conf)

  private val rdd1: RDD[Int] = sc.parallelize(Array(1,2,3,4,5))
  private val rdd2: RDD[String] = sc.parallelize(Array("A","B","C","D","E"))


  // zip:将两个RDD组合成(K,V)形式的RDD,前提是需要两个RDD的partition数量和元素数量都相同
  // 否则会报异常
  private val rdd3: RDD[(Int, String)] = rdd1.zip(rdd2)
  println(rdd3.collect().mkString(" "))


   // sc.parallelize(Array("A","B","C"))  //这种情况下会报错


  sc.stop()
}

在这里插入图片描述

5.2 RDD 依赖关系

**1) RDD 血缘关系

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

在这里插入图片描述

**2) RDD 依赖关系

这里所谓的依赖关系,其实就是两个相邻 RDD 之间的关系
在这里插入图片描述

3) RDD 窄依赖

窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用,

窄依赖我们形象的比喻为独生子女。

4) RDD 宽依赖

宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会 引起 Shuffle,总结:宽依赖我们形象的比喻为多生。

5) RDD 阶段划分

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

在这里插入图片描述

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 的关系。

在这里插入图片描述

5.3 RDD 持久化

1) RDD Cache 缓存

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

// cache 操作会增加血缘关系,不改变原有的血缘关系
println(wordToOneRdd.toDebugString)

// 数据缓存。
wordToOneRdd.cache()

// 可以更改存储级别
//mapRdd.persist(StorageLevel.MEMORY_AND_DISK_2)

存储级别

object StorageLevel {
 val NONE = new StorageLevel(false, false, false, false)
 val DISK_ONLY = new StorageLevel(true, false, false, false)
 val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
 val MEMORY_ONLY = new StorageLevel(false, true, false, true)
 val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
 val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
 val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
 val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
 val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
 val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
 val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
 val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

在这里插入图片描述

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

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

2) RDD CheckPoint 检查点

所谓的检查点其实就是通过将 RDD 中间结果写入磁盘

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

对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。

// 设置检查点路径
sc.setCheckpointDir("./checkpoint1")

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

// 业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
 word => {
 (word, System.currentTimeMillis())
 } }
 
// 增加缓存,避免再重新跑一个 job 做 checkpoint
wordToOneRdd.cache()

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

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

3) 缓存和检查点区别

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

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

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

5.4 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,并除以分区个数取余

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

5.5 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 中,可以调用 sequenceFilekeyClass, valueClass

// 保存数据为 SequenceFile
dataRDD.saveAsSequenceFile("output")

// 读取 SequenceFile 文件
sc.sequenceFile[Int,Int]("output").collect().foreach(println)

object 对象文件

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

// 保存数据
dataRDD.saveAsObjectFile("output")

// 读取数据
sc.objectFile[Int]("output").collect().foreach(println)
5.6 累加器
5.6.1 实现原理

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

val rdd = sc.makeRDD(List(1,2,3,4,5))
// 声明累加器
var sum = sc.longAccumulator("sum");
rdd.foreach(
 num => {
 // 使用累加器
 sum.add(num)
 } )
 
// 获取累加器的值
println("sum = " + sum.value)
5.7 广播变量
5.7.1 实现原理

he()

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

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


**3)** **缓存和检查点区别** 

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

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

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

##### **5.4 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,并除以分区个数取余

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

##### **5.5 RDD** **文件读取与保存** 

Spark 的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。 

文件格式分为:text 文件、csv 文件、sequence 文件以及 Object 文件; 

文件系统分为:本地文件系统、HDFS、HBASE 以及数据库。

➢ **text** **文件**

```scala
// 读取输入文件
val inputRDD: RDD[String] = sc.textFile("input/1.txt")

// 保存数据
inputRDD.saveAsTextFile("output")

sequence 文件

SequenceFile 文件是 Hadoop 用来存储二进制形式的 key-value 对而设计的一种平面文件(Flat File)。在 SparkContext 中,可以调用 sequenceFilekeyClass, valueClass

// 保存数据为 SequenceFile
dataRDD.saveAsSequenceFile("output")

// 读取 SequenceFile 文件
sc.sequenceFile[Int,Int]("output").collect().foreach(println)

object 对象文件

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

// 保存数据
dataRDD.saveAsObjectFile("output")

// 读取数据
sc.objectFile[Int]("output").collect().foreach(println)
5.6 累加器
5.6.1 实现原理

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

val rdd = sc.makeRDD(List(1,2,3,4,5))
// 声明累加器
var sum = sc.longAccumulator("sum");
rdd.foreach(
 num => {
 // 使用累加器
 sum.add(num)
 } )
 
// 获取累加器的值
println("sum = " + sum.value)
5.7 广播变量
5.7.1 实现原理

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值