本编主要基于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来进行计算

【Spark】Spark核心编程_原理


RDD数据处理的流程

【Spark】Spark核心编程_spark_02

1️⃣:​RDD的数据处理方式类似于IO流,也有装饰者设计模式

2️⃣:​RDD的数据只有在调用collect方法时,才会真正的执行业务逻辑操作,之前的封装全是功能的扩展

3️⃣:​RDD它是不保存数据的

​跳转顶部​


RDD核心属性

【Spark】Spark核心编程_大数据_03


分区列表

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

【Spark】Spark核心编程_原理_04


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

【Spark】Spark核心编程_原理_05


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

【Spark】Spark核心编程_数据_06


➢ 首选位置(可选)

计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

【Spark】Spark核心编程_大数据_07

​跳转顶部​


执行原理

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

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

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


➢启动 Yarn 集群环境

【Spark】Spark核心编程_spark_08


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

【Spark】Spark核心编程_big data_09


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

【Spark】Spark核心编程_大数据_10


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

【Spark】Spark核心编程_数据_11


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

​跳转顶部​


创建RDD的几种方式

从集合中创建RDD

@Test
//集合创建
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​​原码展示【Spark】Spark核心编程_大数据_12
    ​nakrRDD​​源码展示【Spark】Spark核心编程_大数据_13
    ​​​跳转顶部​

通过文件创建RDD

@Test
//外部数据(文件)创建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)

【Spark】Spark核心编程_原理_14

​跳转顶部​


从其他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的并行度和分区

@Test
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)
}
}

​跳转顶部​