RDD中的函数传递
在实际开发中我们往往需要自己定义一些对于RDD的操作,那么此时需要主要的是,初始化工作是在Driver端进行的,而实际运行程序是在Executor端进行的,这就涉及到了跨进程通信,是需要序列化的。
传递一个方法
1.创建一个类
class Search(query:String) {
// 过滤包含字符串的数据
def isMatch(s:String):Boolean={
s.contains(query)
}
// 过滤出包含字符串的RDD
def getMatch1(rdd:RDD[String]):RDD[String]={
rdd.filter(isMatch)
}
// 过滤出包含字符串的RDD
def getMatch2(rdd:RDD[String]):RDD[String]={
rdd.filter((x => x.contains(query)))
}
}
2.创建Spark主程序
object SeriTest {
def main(args: Array[String]): Unit = {
// 初始化配置信息及SparkContext
val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
// 创建一个RDD
val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))
// 创建一个Search对象
val search = new Search("h")
// 运用第一个过滤函数并打印结果
val match1: RDD[String] = search.getMatch1(rdd)
match1.collect().foreach(println)
sc.stop()
}
}
3.运行程序
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:345)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:335)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:159)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2299)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:388)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:387)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
at org.apache.spark.rdd.RDD.withScope(RDD.scala:363)
at org.apache.spark.rdd.RDD.filter(RDD.scala:387)
at cn.zut.bigdata.Search.getMatch2(SeriTest.scala:39)
at cn.zut.bigdata.SeriTest$.main(SeriTest.scala:22)
at cn.zut.bigdata.SeriTest.main(SeriTest.scala)
Caused by: java.io.NotSerializableException: cn.zut.bigdata.Search
Serialization stack:
- object not serializable (class: cn.zut.bigdata.Search, value: cn.zut.bigdata.Search@55cff952)
4.问题说明
//过滤出包含字符串的RDD
def getMatch1 (rdd: RDD[String]): RDD[String] = {
rdd.filter(isMatch)
}
在这个方法中所调用的方法isMatch()是定义在Search这个类中的,实际上调用的是this. isMatch(),this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。
5.解决方案
使类继承scala.Serializable即可。
class Search(query:String) extends Serializable{...}
也可以如下所示解决办法
package cn.zut.bigdata
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
class Search(query:String) {
// 过滤包含字符串的数据
def isMatch(s:String):Boolean={
s.contains(query)
}
// 过滤出包含字符串的RDD
def getMatch1(rdd:RDD[String]):RDD[String]={
rdd.filter(isMatch)
}
// 过滤出包含字符串的RDD
def getMatch2(rdd:RDD[String]):RDD[String]={
val q = query
rdd.filter((x => x.contains(q)))
}
}
传递一个属性
1.创建Spark主程序
object TransmitTest {
object SeriTest {
def main(args: Array[String]): Unit = {
// 初始化配置信息及SparkContext
val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
// 创建一个RDD
val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))
// 创建一个Search对象
val search = new Search("h")
// 运用第二个过滤函数并打印结果
val match1: RDD[String] = search.getMatch2(rdd)
match1.collect().foreach(println)
sc.stop()
}
}
2.运行程序还是会报序列化的问题
3.问题说明
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
rdd.filter(x => x.contains(query))
}
在这个方法中所调用的方法query是定义在Search这个类中的字段,实际上调用的是this. query,this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。
4.解决方案
1)使类继承scala.Serializable即可。
class Search(query:String) extends Serializable{...}
2)将类变量query赋值给局部变量
修改getMatche2为
// 过滤出包含字符串的RDD
def getMatch2(rdd:RDD[String]):RDD[String]={
val q = query
rdd.filter((x => x.contains(q)))
}
RDD依赖关系
Lineage
RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
(1)读取一个HDFS文件并将其中内容映射成一个个元组
scala> val wordAndOne = sc.textFile("hdfs://node01:9000/hyk/spark/words.txt").flatMap(_.split(" ")).map((_,1))
wordAndOne: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[17] at map at <console>:24
(2)统计每一种key对应的个数
scala> val wordAndCount = wordAndOne.reduceByKey(_+_)
wordAndCount: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[18] at reduceByKey at <console>:25
(3)查看“wordAndOne”的Lineage
scala> wordAndOne.toDebugString
res0: String =
(2) MapPartitionsRDD[17] at map at <console>:24 []
| MapPartitionsRDD[16] at flatMap at <console>:24 []
| hdfs://node01:9000/hyk/spark/words.txt MapPartitionsRDD[15] at textFile at <console>:24 []
| hdfs://node01:9000/hyk/spark/words.txt HadoopRDD[14] at textFile at <console>:24 []
(4)查看“wordAndCount”的Lineage
scala> wordAndCount.toDebugString
res1: String =
(2) ShuffledRDD[18] at reduceByKey at <console>:25 []
+-(2) MapPartitionsRDD[17] at map at <console>:24 []
| MapPartitionsRDD[16] at flatMap at <console>:24 []
| hdfs://node01:9000/hyk/spark/words.txt MapPartitionsRDD[15] at textFile at <console>:24 []
| hdfs://node01:9000/hyk/spark/words.txt HadoopRDD[14] at textFile at <console>:24 []
(5)查看“wordAndOne”的依赖类型
scala> wordAndOne.dependencies
res2: Seq[org.apache.spark.Dependency[_]] = List(org.apache.spark.OneToOneDependency@57567018)
(6)查看“wordAndCount”的依赖类型
scala> wordAndCount.dependencies
res3: Seq[org.apache.spark.Dependency[_]] = List(org.apache.spark.ShuffleDependency@61b9f9a1)
注意:RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)。
窄依赖
窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女
宽依赖
宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle,总结:宽依赖我们形象的比喻为超生
DAG
DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的Stage,对于窄依赖,partition的转换处理在Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。
任务划分
RDD任务切分中间分为:Application、Job、Stage和Task
1)Application:初始化一个SparkContext即生成一个Application
2)Job:一个Action算子就会生成一个Job
3)Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。
4)Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。
注意:Application->Job->Stage-> Task每一层都是1对n的关系
RDD缓存
RDD通过persist方法或cache方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。
但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
通过查看源码发现cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份,Spark的存储级别还有好多种,存储级别在object StorageLevel中定义的。
在存储级别的末尾加上“_2”来把持久化数据存为两份
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
(1)创建一个RDD
scala> val rdd = sc.makeRDD(Array("zut"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[20] at makeRDD at <console>:24
(2)将RDD转换为携带当前时间戳不做缓存
scala> val nocache = rdd.map(_.toString+System.currentTimeMillis)
nocache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[21] at map at <console>:25
(3)多次打印结果
scala> nocache.collect
res7: Array[String] = Array(zut1583565968704)
scala> nocache.collect
res8: Array[String] = Array(zut1583565971198)
scala> nocache.collect
res9: Array[String] = Array(zut1583565972444)
scala> nocache.collect
res10: Array[String] = Array(zut1583565973419)
(4)将RDD转换为携带当前时间戳并做缓存
scala> val cache = rdd.map(_.toString+System.currentTimeMillis).cache
cache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[22] at map at <console>:25
(5)多次打印做了缓存的结果
scala> cache.collect
res11: Array[String] = Array(zut1583566022971)
scala> cache.collect
res12: Array[String] = Array(zut1583566022971)
scala> cache.collect
res13: Array[String] = Array(zut1583566022971)
scala> cache.collect
res14: Array[String] = Array(zut1583566022971)
RDD CheckPoint
Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。
案例实操:
(1)设置检查点
scala> sc.setCheckpointDir("hdfs://node01:9000/hyk/checkpoint")
(2)创建一个RDD
scala> val rdd = sc.parallelize(Array("zut"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[23] at parallelize at <console>:24
(3)将RDD转换为携带当前时间戳并做checkpoint
scala> val ch = rdd.map(_+System.currentTimeMillis)
ch: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[24] at map at <console>:25
scala> ch.checkpoint
(4)多次打印结果
scala> ch.collect
res17: Array[String] = Array(zut1583566427222)
scala> ch.collect
res18: Array[String] = Array(zut1583566427407)
scala> ch.collect
res19: Array[String] = Array(zut1583566427407)
scala> ch.collect
res20: Array[String] = Array(zut1583566427407)
scala> ch.collect
res21: Array[String] = Array(zut1583566427407)