引子
Spark计算框架为了能够对数据进行高并发和高吞吐的处理,封装了三大数据结构,用于处理不同的应用场景。三大数据结构分别是:
1)RDD : 弹性分布式数据集
2)累加器:分布式共享只写变量
3)广播变量:分布式共享只读变量
ps:数据结构:简单理解为 数据与逻辑的组织形式和存储方式
RDD
1.RDD定义?
RDD(Resilient Distributed Dataset)为弹性分布式数据集,是Spark中最基本的数据处理模型。代码中是一个抽象类(子类多,使用装饰者模式扩充功能),它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。
2.怎么理解RDD为Spark中封装的最小计算单元?
仅提供数据和逻辑的结构,逻辑简单,扩展性强,当有复杂的逻辑,只需将多个RDD做关联,随task传给Executer
3.思考如何通过组合RDD实现复杂的逻辑?
装饰者设计模式:以IO为例,字节流通过缓冲区包装成缓冲流,缓冲区超过阈值才会打印(批处理,提高性能),转换流也是,核心还是低级流起作用,以此方式扩展了新的功能,字节流通过字符流转为字符,再通过缓冲流,按行处理,readLine之前(new:创建包装流对象)都没有对数据的读取,它才是触发作业的执行.即惰性求值,没有行动算子的执行,context都不会启动
以此方式理解RDD的数据处理方式,同上:RDD的数据只有调用collect方法时,才会真正执行业务逻辑的操作,之前的封装全是功能的扩展,与IO不同,RDD没有buff临时存数据,数据只有流转没有存储
4.如何将RDD分解为多个Task?
不仅提供计算逻辑,还将数据切分,多个分区将数据分区以提高task并行度
5.RDD的综合特征?
1)弹性:①存储的弹性:内存与磁盘的自动切换;②容错的弹性:数据丢失可以自动恢复;(由读取文件和分区逻辑找到重读) ③计算的弹性:计算出错重试机制;④分片的弹性:可根据需要重新分片。
2)分布式:数据存储在大数据集群不同节点上
3)数据集:RDD封装了计算逻辑,并不保存数据
4)数据抽象:RDD是一个抽象类,需要子类具体实现
5)不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑(通过装饰者模式的丰富功能得到新的RDD)
6)可分区、并行计算
6.如何确定计算任务的首选位置?
1)计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
2)判断计算任务task发给哪个节点最优?就近原则:优先选数据所在节点,不用拉取,减少IO
3)为什么不先考虑任务发送的节点就近呢?移动数据不如移动计算
7.Yarn环境中,RDD的工作原理?
1)启动Yarn集群环境
2)Spark通过申请资源创建调度节点和计算节点
3)Spark框架根据需求将计算逻辑根据分区划分成不同的任务,多个RDD的多个逻辑形成关联,分解成多个task,放入任务池后方便调度
4)调度节点将任务根据计算节点状态发送到对应的计算节点进行计算
从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,并生成Task发送给Executor节点执行计算。
代码演示RDD的工作原理:
//Driver类
object Driver {
def main(args: Array[String]): Unit = {
val socket1 = new Socket("localhost", 9999)
val socket2 = new Socket("localhost", 8888)
val ds = new DS
val out1 = socket1.getOutputStream
val objOut1 = new ObjectOutputStream(out1)
val task1 = new Task
task1.logic=ds.logic
task1.datas=ds.datas.take(2)
objOut1.writeObject(task1)
objOut1.close()
socket1.close()
println("task1发送完毕")
val out2 = socket2.getOutputStream
val objOut2 = new ObjectOutputStream(out2)
val task2 = new Task
task2.logic=ds.logic
task2.datas=ds.datas.takeRight(2)
objOut2.writeObject(task2)
objOut2.close()
socket2.close()
println("task2发送完毕")
}
}
//模拟数据结构,仅提供数据和逻辑
class DS extends Serializable {
val datas = List(1, 2, 3, 4)
/*val logic: Int => Int = (num: Int) => {
num * 2
}*/
val logic:Int=>Int=_ *2
}
//task负责计算
class Task extends Serializable {
var datas :List[Int]=_
var logic:Int=>Int=_
//计算
def compute()={
datas.map(logic)
}
}
//计算节点1
object Executor {
def main(args: Array[String]): Unit = {
val server = new ServerSocket(9999)
println("启动服务器...")
val socket = server.accept()
val in = socket.getInputStream
val objIn = new ObjectInputStream(in)
val task = objIn.readObject().asInstanceOf[Task]
println(task.compute())
objIn.close()
socket.close()
server.close()
}
}
//计算节点2
object Executor2 {
def main(args: Array[String]): Unit = {
val server = new ServerSocket(8888)
println("启动服务器...")
val socket = server.accept()
val in = socket.getInputStream
val objIn = new ObjectInputStream(in)
val task = objIn.readObject().asInstanceOf[Task]
println(task.compute())
objIn.close()
socket.close()
server.close()
}
}