目录
一、spark简介
spark是什么
Apache Spark是专门为大数据处理而设计的通用的计算引擎。spark拥有MapReduce所具有的优点,但不同于Map Reduce的是Job中间输出结果可以缓存到内存中,从而不再需要读写HDFS,减少磁盘数据交互,因此Spark能更好的适应机器学习和数据挖掘等需要迭代的算法。
Spark提供了Spark RDD 、 Spark SQL 、 Spark Streaming 、 Spark MLlib 、 Spark GraphX等技术组件,可以一站式地完成大数据领域的离线批处理、交互式查询、流式计算、机器学习、图计算等常见的任务。这就是 spark 一站式开发的特点。
spark的特征
- 更快的速度
- 内存计算下,Spark 比 Hadoop 快100倍。
- 易用性
- Spark 提供了80多个高级运算符。
- 通用性
- Spark 提供了大量的库,包括Spark Core、Spark SQL、Spark Streaming、MLlib、GraphX。 开发者可以在同一个应用程序中无缝组合使用这些库。
- 支持多种资源管理器
- Spark 支持 Hadoop YARN,Apache Mesos,及其自带的独立集群管理器
- Spark生态系统
- Shark:Shark基本上就是在Spark的框架基础上提供和Hive一样的HiveQL命令接口,为了最大程度的保持和Hive的兼容性,Spark使用了Hive的API来实现query Parsing和 Logic Plan generation,最后的PhysicalPlan execution阶段用Spark代替HadoopMapReduce。通过配置Shark参数,Shark可以自动在内存中缓存特定的RDD,实现数据重用,进而加快特定数据集的检索。同时,Shark通过UDF用户自定义函数实现特定的数据分析学习算法,使得SQL数据查询和运算分析能结合在一起,最大化RDD的重复使用。
- SparkR:SparkR是一个为R提供了轻量级的Spark前端的R包。 SparkR提供了一个分布式的data frame数据结构,解决了 R中的data frame只能在单机中使用的瓶颈,它和R中的data frame 一样支持许多操作,比如select,filter,aggregate等等。(类似dplyr包中的功能)这很好的解决了R的大数据级瓶颈问题。 SparkR也支持分布式的机器学习算法,比如使用MLib机器学习库。SparkR为Spark引入了R语言社区的活力,吸引了大量的数据科学家开始在Spark平台上直接开始数据分析之旅。
二、Spark RDD
RDD基本概念
- RDD(Resilient Distributed Datasets),弹性分布式数据集,是分布式内存的一个抽象概念。RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,只能通过在其他RDD执行确定的转换操作(如map、join和group by)而创建,然而这些限制使得实现容错的开销很低。
- 对开发者而言,RDD可以看作是Spark的一个对象,它本身运行于内存中,如读文件是一个RDD,对文件计算是一个RDD,结果集也是一个RDD ,不同的分片、 数据之间的依赖 、key-value类型的map数据都可以看做RDD。
- RDD具备像MapReduce等数据流模型的容错特性,并且允许开发人员在大型集群上执行基于内存的计算。
- 现有的数据流系统对两种应用的处理并不高效:一是迭代式算法,这在图应用和机器学习领域很常见;二是交互式数据挖掘工具。这两种情况下,将数据保存在内存中能够极大地提高性能。
- 为了有效地实现容错,RDD提供了一种高度受限的共享内存,即RDD是只读的,并且只能通过其他RDD上的批量操作来创建。尽管如此,RDD仍然足以表示很多类型的计算,包括MapReduce和专用的迭代编程模型(如Pregel)等。
- RDD是只读的、分区记录的集合。RDD只能基于在稳定物理存储中的数据集和其他已有的RDD上执行确定性操作来创建。这些确定性操作称之为转换,如map、filter、groupBy、join。
- RDD含有如何从其他RDD衍生(即计算)出本RDD的相关信息(即Lineage),据此可以从物理存储的数据计算出相应的RDD分区。
- RDD作为数据结构,本质上是一个只读的分区记录集合。一个RDD可以包含多个分区,每个分区就是一个dataset片段。
- RDD是一个容错的、并行的数据结构,可以让用户显式地将数据存储到磁盘和内存中,并能控制数据和分区。逻辑上可以认为RDD是一个分布式的集合
- Spark的核心数据模型是RDD,但RDD是个抽象类,具体由各子类实现,如MappedRDD、MapPartitionsRDD、ShuffledRDD、ReliableCheckpointRDD等子类。Spark将常用的大数据操作都转化成为RDD的子类。
RDD五大属性
- RDD是由一系列的Partition组成的
- 函数是作用在每一个 partition/split 上
- RDD之间有一系列的依赖关系
- 分区器是作用在 (K,V) 格式的 RDD 上
- RDD提供一系列最佳的计算位置
HDFS与Partition
- hdfs中的block是分布式存储的最小单元,类似于盛放文件的盒子,一个文件可能要占多个盒子,但一个盒子里的内容只可能来自同一份文件。假设block设置为128M,你的文件是260M,那么这 份文件占3个block(128+128+4)。这样的设计虽然会有一部分磁盘空间的浪费,但是整齐的block大小,便于快速找到、读取对应的内容。(p.s. 考虑到hdfs冗余设计,默认三份拷贝,实际上3*3=9个block的物理空间。)
- spark中的partition是弹性分布式数据集RDD的最小单元,RDD是由分布在各个节点上的partition组成的。partition是指的spark在计算过程中,生成的数据在计算空间内最小单元,同一份数据(RDD)的partition大小不一,数量不定,是根据application里的算子和最初读入的数据分块数量决定的
- block位于存储空间、partition位于计算空间,block的大小是固定的、partition大小是不固定的, block是有冗余的、不会轻易丢失,partition(RDD)没有冗余设计、丢失之后重新计算得到.
- Spark从HDFS读入文件的分区数默认等于HDFS文件的块数(blocks),HDFS中的block是分布式存储的最小单元。如果我们上传一个30GB的非压缩的文件到HDFS,HDFS默认的块容量大小128MB,因此该文件在HDFS上会被分为235块(30GB/128MB);Spark读取SparkContext.textFile()读取该文件,默认分区数等于块数即235。
RDD流程图
注意
- textFile 方法底层封装的是 MR 读取文件的方式,读取文件之前先进行 split 切片,默认 split大小是一个 block 大小
- RDD实际上不存储数据,这里方便理解,暂时理解为存储数据
- 什么是K,V格式的RDD ?
- 如果RDD 里面存储的数据都是二元组对象,那么这个 RDD 我们就叫做 K,V格式的RDD
- 哪里体现 RDD 的弹性(容错)?
- partition数量,大小没有限制,体现了 RDD 的弹性
- RDD之间依赖关系,可以基于上一个 RDD 重新计算出 RDD
- 哪里体现 RDD 的分布式?
- RDD是由 Partition 组成, partition 是分布在不同节点上的
- RDD提供计算最佳位置,体现了数据本地化。体现了大数据中“计算移动数据不移动”的理念
Lineage血统
RDD的最重要的特性之一就是血缘关系(Lineage ),它描述了一个 RDD 是如何从父 RDD 计算得来的。如果某个 RDD 丢失了,则可以根据血缘关系,从父 RDD 计算得来。
三、Spark 算子
Spark 记录了 RDD 之间的生成和依赖关系。但是只有当 F 进行行动操作时,Spark 才会根据 RDD的依赖关系生成 DAG,并从起点开始
转换算子
- Transformations 类算子叫做转换算子(本质就是函数), Transformations 算子是延迟执行,也叫懒加载执行。
- 常见Transformations类算子
- filter:过滤符合条件的记录数, true 保留, false 过滤掉。
- map:将一个 RDD 中的每个数据项,通过 map 中的函数映射变为一个新的元素。特点:输入 一条,输出一条数据。
- flatMap:先 map 后 flat 。与 map 类似,每个输入项可以映射为0到多个输出项。
- sample:随机抽样算子,根据传进去的小数按比例进行有放回或者无放回的抽样。
- reduceByKey 将相同的 Key 根据相应的逻辑进行处理。
- sortByKey / sortBy 作用在 K,V格式的RDD 上,对 key 进行升序或者降序排序。
package com.libing.spark.operator
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 转换算子是对rdd相互转换
* filter/map/flatMap/sample/reduceByKey
*
* @author liar
* @date 2022/9/4 10:31
* @version 1.0
*/
object TransformationsFun {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
conf.setMaster("local").setAppName("transformation_operator")
val sc: SparkContext = new SparkContext(conf)
//数据(rdd)的产生,rdd之间的相互转换
val file: String = TransformationsFun.getClass.getClassLoader.getResource("wc.txt").getFile
val lineRDD: RDD[String] = sc.textFile(file)
// println(sc.defaultMinPartitions)
// println(sc.defaultParallelism)
//
// /**
// * filter
// * filter不会改变数据的整体结构
// */
// val resultRDD: RDD[String] = lineRDD.filter(x => {
// if (x.contains("shanghai"))
// true
// else
// false
// })
// resultRDD.foreach(println)
// println("------------------------")
//
//
// /**
// * map & flatMap
// */
// val value: RDD[Array[String]] = lineRDD.map(x => x.split(" "))
// val value1: RDD[String] = lineRDD.flatMap(_.split(" "))
// value.foreach(println)
// value1.foreach(println)
// println("------------------------")
//
//
// /**
// * reduceByKey
// */
// lineRDD.map(x => (x, 1)).reduceByKey(_ + _).foreach(println)
/**
* sortBYKey & sortBy
* 排序,sortBy指定key再去排序
* 默认升序,false降序
*/
val result: RDD[(String, Int)] = lineRDD.flatMap(x => x.split(" ")).map(x => (x, 1)).reduceByKey((x, y) => x + y)
result.sortBy(_._2).foreach(println)
result.sortBy(_._2, false).foreach(println)
result.sortBy(_._1, false).foreach(println)
result.sortByKey().foreach(println)
/**
* sample(抽样)
*/
lineRDD.sample(true, 0.5).foreach(println)
sc.stop()
}
}
wc.txt文件如下
行动算子
Action 类算子叫做行动算子, Action 类算子是触发执行。
一个application 应用程序中有几个 Action 类算子执行,就有几个 job 运行。
常见Action 类算子
count :返回数据集中的元素数。会在结果计算完成后回收到 Driver 端。
take(n) :返回一个包含数据集前 n 个元素的集合。
first:效果等同于 take(1) ,返回数据集中的第一个元素。
foreach:循环遍历数据集中的每个元素,运行相应的逻辑。
collect:将计算结果回收到 Driver 端。
package com.libing.spark.operator
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author liar
* @date 2022/9/4 12:48
* @version 1.0
*/
object ActionFun {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
conf.setMaster("local").setAppName("action_operator")
val sc: SparkContext = new SparkContext(conf)
val file: String = TransformationsFun.getClass.getClassLoader.getResource("wc.txt").getFile
val lineRDD: RDD[String] = sc.textFile(file)
/**
* 行动算子
* count foreach collect take first
*/
println("------行动算子开始-------")
println("======foreach开始=======")
lineRDD.foreach(println)
println("======foreach结束=======")
println("======count开始=======")
//计算数据集中存在的元素数量
println(lineRDD.count())
println("======count结束=======")
println("======collect开始=======")
lineRDD.collect().foreach(println)
//1.collect的作用
//Spark内有collect方法,是Action操作里边的一个算子,这个方法可以将RDD类型的数据转化为数组,同时会从远程集群里拉取数据到driver端。
//2.已知的弊端
//首先,collect是Action里边的,根据RDD的惰性机制,真正的计算发生在RDD的Action操作。那么,一次collect就会导致一次Shuffle,而一次Shuffle调度一次stage,然而一次stage包含很多个已分解的任务碎片Task。这么一来,会导致程序运行时间大大增加,属于比较耗时的操作,即使是在local模式下也同样耗时。
//其次,从环境上来讲,本机local模式下运行并无太大区别,可若放在分布式环境下运行,一次collect操作会将分布式各个节点上的数据汇聚到一个driver节点上,而这么一来,后续所执行的运算和操作就会脱离这个分布式环境而相当于单机环境下运行,这也与Spark的分布式理念不合。
//最后,将大量数据汇集到一个driver节点上,并且像这样val arr = data.collect(),将数据用数组存放,占用了jvm堆内存,可想而知,是有多么轻松就会内存溢出。
//3.如何规避
//若需要遍历RDD中元素,大可不必使用collect,可以使用foreach语句;
//若需要打印RDD中元素,可用take语句,返回数据集前n个元素,data.take(1000).foreach(println),这点官方文档里有说明;
//若需要查看其中内容,可用saveAsTextFile方法。
//总之,单机环境下使用collect问题并不大,但分布式环境下尽量规避