1 RDD序列化
从计算的角度, 算子以外的代码都是在Driver端执行, 算子里面的代码都是在Executor端执行。算子内用到算子外的数据,这样就形成了闭包的效果,算子外的数据需要序列化传值给Executor端执行,闭包检测。
这里的闭包指:就算Executor端的代码一行没走,只要使用了算子外的对象,就算闭包,即改变了变量的生命周期,就需要闭包检测
实例:
object Spark01_RDD_Serial {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[4]").setAppName("Serial")
val sc = new SparkContext(sparkConf)
val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark", "hive", "atguigu"))
//3.1创建一个Search对象
val search = new Search("hello")
//3.2 函数传递,打印:ERROR Task not serializable
// search.getMatch1(rdd).collect().foreach(println)
//3.3 属性传递,打印:ERROR Task not serializable
search.getMatch2(rdd).collect().foreach(println)
sc.stop()
}
}
//构造参数作为类的私有属性,一旦使用就需要类进行闭包检测
class Search(query:String){
def isMatch(s: String): Boolean = {
s.contains(query)
}
// 函数序列化案例
def getMatch1 (rdd: RDD[String]): RDD[String] = {
//rdd.filter(this.isMatch)
rdd.filter(isMatch)
}
// 属性序列化案例
def getMatch2(rdd: RDD[String]): RDD[String] = {
//rdd.filter(x => x.contains(this.query))
// rdd.filter(x => x.contains(query))
//1.混入序列化特质2.样例类3.将构造参数值传递给变量
val q = query
rdd.filter(x => x.contains(q))
}
}
2 RDD依赖关系
相邻的新的RDD依赖旧的RDD,多个连续的RDD的依赖关系,称之为血缘,同时RDD会保存血缘关系,但不会保存数据,
容错:RDD部分分区数据丢失时,根据血缘关系将数据源重新读取进行计算
2.1 RDD窄依赖
一个任务就能对该分区的所有数据执行完全部逻辑且得到正确结果,新的RDD一个分区的数据依赖于旧的RDD一个分区的数据
2.2 RDD宽依赖
宽依赖表示同一个父RDD的Partition被多个子RDD的Partition依赖,会引起Shuffle,新的RDD一个分区的数据依赖于旧的RDD多个分区的数据
任务数相较窄依赖增加,且操作分为多个阶段,前一个阶段的task执行完才往下
2.3 RDD阶段划分
DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。例如,DAG记录了RDD的转换过程和任务的阶段。
当RDD中存在shuffle依赖时,阶段自动加一,因此阶段数=shuffle依赖的数量+1(result依赖)
2.4 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的关系。
3 RDD持久化
3.1 RDD Cache缓存
RDD通过Cache或者Persist方法将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在JVM的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的action算子时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
Spark会自动对一些Shuffle操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点Shuffle失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist或cache。
3.2 RDD CheckPoint
将RDD中间结果写入磁盘,由于血缘依赖过长会造成容错成本过高,在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。
3.3 两者区别
cache:将数据临时存储在内存中进行数据重用,会在血缘关系中添加新的依赖.一旦出现问题,可以从头读取数据
persist:将数据临时存储在磁盘文件中进行数据重用,涉及到磁盘IO,性能较低,但是数据安全,如果作业执行完毕,临时保存的数据文件就会丢失
checkpoint:将数据长久地保存在磁盘文件中进行数据重用,涉及到磁盘IO,性能较低,但是数据安全,所以一般情况下,是需要和cache联合使用,执行过程中,会切断血缘关系,重新建立新的血缘关系,checkpoint等同于改变数据源
4 自定义分区器
object Spark01_RDD_Part {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[4]").setAppName("part")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(
("nba", "xxxxxxxxxx"),
("cba", "xxxxxxxxxx"),
("wnba", "xxxxxxxxxx"),
("nba", "xxxxxxxxxx")
), 3)
val partRDD = rdd.partitionBy(new MyPartitioner)
partRDD.saveAsTextFile("output")
sc.stop()
}
}
class MyPartitioner extends Partitioner{
override def numPartitions: Int = 3
override def getPartition(key: Any): Int = {
key match {
case "nba" =>0
case "cba" =>1
case _ =>2
}
}
}
5 RDD文件读取与保存
//文件保存
val sparkConf = new SparkConf().setMaster("local[4]").setAppName("part")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(
("a", 1),
("b", 2),
("c", 3)
))
rdd.saveAsTextFile("output1")
rdd.saveAsObjectFile("output2")
rdd.saveAsSequenceFile("output3")
//文件读取
sc.textFile("output1").collect().foreach(println)
sc.objectFile("output2").collect().foreach(println)
sc.sequenceFile[String,Int]("output3").collect().foreach(println)