本编主要基于B站尚硅谷的视频及文档做出的一些改写和添加自己的理解而编写
B站尚硅谷Spark视频讲解链接: 点击跳转.
Spark 核心编程
概述
Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于处理不同的应用场景。三大数据结构分别是
1️⃣RDD : 弹性分布式数据集
2️⃣累加器:分布式共享只写变量
3️⃣广播变量:分布式共享只读变量
RDD原理解释
什么叫做RDD?
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行- 计算的集合。
特性如下:
- 1️⃣ :弹性➢ 存储的弹性:内存与磁盘的自动切换;➢ 容错的弹性:数据丢失可以自动恢复;➢ 计算的弹性:计算出错重试机制;➢ 分片的弹性:可根据需要重新分片。
- 2️⃣:分布式:数据存储在大数据集群不同节点上
- 3️⃣:数据集:RDD 封装了计算逻辑,并不保存数据
- 4️⃣:数据抽象:RDD 是一个抽象类,需要子类具体实现
- 5️⃣:不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在新的 RDD 里面封装计算逻辑
- 6️⃣:可分区、并行计算
RDD是最小的计算单元,RDD将程序分成一个个的Task,并将Task发送到Executor来进行计算
RDD数据处理的流程
1️⃣:RDD的数据处理方式类似于IO流,也有装饰者设计模式
2️⃣:RDD的数据只有在调用collect方法时,才会真正的执行业务逻辑操作,之前的封装全是功能的扩展
3️⃣:RDD它是不保存数据的
跳转顶部
RDD核心属性
➢ 分区列表
RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
➢ RDD 之间的依赖关系
RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系
➢ 分区器(可选)
当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区
➢ 首选位置(可选)
计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
跳转顶部
执行原理
1️⃣:从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。
2️⃣:Spark 框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。
3️⃣:RDD 是 Spark 框架中用于数据处理的核心模型,接下来我们看看,在 Yarn 环境中,RDD的工作原理:
➢启动 Yarn 集群环境
➢Spark 通过申请资源创建调度节点和计算节点
➢Spark 框架根据需求将计算逻辑根据分区划分成不同的任务
➢调度节点将任务根据计算节点状态发送到对应的计算节点进行计算
从以上流程可以看出 RDD 在整个流程中主要用于将逻辑进行封装,并生成 Task 发送给Executor 节点执行计算,接下来我们就一起看看 Spark 框架中 RDD 是具体是如何进行数据处理的。
跳转顶部
创建RDD的几种方式
从集合中创建RDD
//集合创建
def rddCreationLocal(): Unit = {
//创建SparkConf
val conf = new SparkConf().setMaster("local[6]").setAppName("rddCreationLocal")
//创建SparkContext
val sc = new SparkContext(conf)
val seq1 = Seq("hello", "world", "HI")
val seq2 = Seq(1, 2, 3)
/**
* parallelize:并行
*/
//可以不指定分区数
val rdd1: RDD[String] = sc.parallelize(seq1)
//要指定分区数
val rdd2: RDD[Int] = sc.makeRDD(seq2, 2)
//输出
rdd2.collect().foreach(println(_))
}
问:既然使用parallelize
和makeRDD
都可以,那么这俩者是否有什么区别?
- 答:其实makeRDD方法在底层实现时就是调用了rdd对象的parallelize方法
parallelize
原码展示
nakrRDD
源码展示
跳转顶部
通过文件创建RDD
//外部数据(文件)创建RDD
def rddCreationFiles(): Unit = {
//创建SparkConf
val conf = new SparkConf().setMaster("local[6]").setAppName("rddCreationFiles")
//创建SparkContext
val sc = new SparkContext(conf)
val rdd1: RDD[String] = sc.textFile("data/wordcount.txt")
//使用通配符来读取
val rdd2 = sc.textFile("data/word*.txt")
rdd1.collect().foreach(println)
}
注意点
- path路径默认以当前环境的根路径为基准,可以写绝对路径,也可以写相对路径
- 如果path路径使目录时,那么就代表读取多个文件
- path可以使用通配符来读取
思考:当我们读取多个文件的时候,如何分辨数据是来自哪一个文件的?
val rdd3 = sc.wholeTextFiles("data/wordcount.txt")
rdd3.collect().foreach(println)
跳转顶部
从其他RDD衍生
//1.创建SparkContext
val conf = new SparkConf().setMaster("local[6]").setAppName("wordCount")
val sc = new SparkContext(conf)
//2.加载文件
val rdd1: RDD[String] = sc.textFile("data/wordcount.txt")
//3.处理
//拆分
val rdd2: RDD[String] = rdd1.flatMap(item => item.split(" "))
//指定词频
val rdd3: RDD[(String, Int)] = rdd2.map(item => (item, 1))
//聚合
val rdd4: RDD[(String, Int)] = rdd3.reduceByKey((curr, agg) => curr + agg)
//4.得到结果
val result: Array[(String, Int)] = rdd4.collect()
result.foreach(item => println(item))
RDD的并行度和分区
def partitionAndParallelize():Unit = {
//创建SparkConf
val conf = new SparkConf()
.setMaster("local[6]")
.setAppName("partitionAndParallelize")
.set("spark.default.parallelism","5")//设置默认分区数
//创建SparkContext
val sc = new SparkContext(conf)
//makeRDD的第二个参数可以不传递,不传递就会使用默认值(默认值为当前最处理器的最大核数)
val rdd = sc.makeRDD(
List(1,2,3,4)
)
//将数据保存为分区文件
rdd.saveAsTextFile("output")
}
注意点
- makeRDD的第二个参数可以不传递,不传递就会使用默认值(默认值为当前最处理器的最大核数)
数据分配的规律
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)
}
}
跳转顶部