文章目录
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上的文件
从Linux模式下查看HDFS下文件
查看HDFS上指定文件内容
- 读取文件
sc.textFile("hdfs:///kb10/wordcount.txt")
---如果想指定本地Linux的文件,写法如下:
sc.textFile("file:///root/sotware/test.txt")
-
分割
sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" "))
-
对单词计数,转换成(hello,1),(spark,1 )这种形式
sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1))
-
统计
sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1)) .reduceByKey(_+_)
-
输出结果
sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1)) .reduceByKey(_+_).collect
-
遍历
sc.textFile("hdfs:///kb10/wordcount.txt").flatMap(x=>x.split(" ")).map(x=>(x,1)) .reduceByKey(_+_).collect.foreach(println)
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安装
-
上传安装包
-
解压
cd /opt/download/hadoop tar -zxvf spark-2.4.4-bin-hadoop2.6.tgz -C /opt/software/hadoop
-
配置环境变量
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 进程
如果Worker启动不了的时候,做以下操作:
[root@single sbin]# vi spark-config.sh
添加JDK路径
6.启动spark-shell 测试Scala交互式环境
spark-shell --master spark://192.168.199.130:7077
可以通过浏览器访问192.168.199.130:8080/
测试Spark on YARN
spark-shell --master yarn
----单节点可配可不配置项
先把 slaves.template 重命名为 slaves
cp slaves.template slaves
mv slaves.template slaves
vim slaves
指定下worker会运行在哪些节点上
single
......
......
安装问题
- web访问不了
- 防火墙是否关闭
- 主机映射是否配置
- 服务是否挂掉
- 端口是否冲突
- 查看错误日志
$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,负责实际执行任务。
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运行架构特点:
- 每个Application都有自己专属的Executor进程,并且该进程在Application运行期间一直驻留。Executor进程以多线程的方式运行Task。
- Spark运行过程与资源管理器无关,只要能够获取Executor进程并保存通信即可。
- 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 主要提供了两个方法:parallelize 和 makeRDD
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),决定这个值是属于那个分区的。
-
Hash 分区:对于给定的 key,计算其 hashCode,并除以分区个数取余
-
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 会为每个任务分别发送。