1.RDD的三大特性
自动容错:(基于Lineage血统,即会保存创建RDD的相关转换(算子操作))
位置感知性调度:为了尽量满足数据本地性,Task调度会依次从以下位置查找是否有计算所需的数据:
1)缓存,即待计算的数据RDD执行过cache()操作
2)Checkpoint:即待计算的数据RDD是否执行过checkpoint()操作,数据检查点保存
3)rdd.preferedLocations():以Hadoop RDD为例,Spark会去查找待计算的数据RDD对应的partition所对应的Block所在的节点位置,即为Task分配调度的最佳位置
可伸缩性:RDD[A]->RDD[B]
2.RDD的五种属性
1)一组分片(partitions):即数据集的基本组成单位(数据并行度),默认值是程序分配的CPU Core数
2)一个计算每个分区的函数:每个RDD都会实现compute函数,compute函数会对迭代器进行复合
3)RDD之间的依赖关系:RDD[A] = > RDD[B]
4)一个partitioner:RDD的分片函数,一般分为两种:基于哈希的HashPartitioner;基于范围的RangePartitioner
5)一个列表:存取每个Partition的优先位置
3.RDD创建:
1)由已存在的Scala集合创建 valrdd=sc.parallelize(list)
2)外部数据源创建 HDFS,S3,等等 valtext=sc.textFile(“hdfs://localhost:9000/test.txt”)
RDD支持的算子操作:
1)Transformation算子:即从现有的数据集创建新的数据集 RDD[A] => RDD[B]
2)Action算子:RDD[A] => result 由RDD得到计算结果,并返回给Driver
RDD[A] = > RDD[B] //执行groupByKey或者reduceByKey时相当于RDD[A]执行shuffle操作得到shuffled RDD(RDD[A]对应于Shuffled RDD而言是宽依赖,Shuffled RDD对应于RDD[B]而言是窄依赖),
常见的算子操作:
1.Transformation算子(延迟执行)
1)map:RDD[A] => RDD[B],RDD[B]中的元素由RDD[A]中的每个元素执行func函数得到
2)filter:RDD[A] =>RDD[B],RDD[B]中的元素由RDD[A]中的每个元素执行func函数为true的元素组成
3)flatMap:RDD[A] => RDD[B],RDD[B]中的元素由RDD[A]中的每个元素执行func函数得到(返回结果是一个序列),即单个元素输入,多个元素输出
4)mapPartitions:RDD[A] =>RDD[B],RDD[B]中的每个元素由RDD[A]中的每个partition执行func函数得到,即输入一个partition(多个元素),输出一个序列
5)mapPartitionsWithSplit:类似于mapPartitions,但func需要指定Index
6)sample:RDD[A] => RDD[B],执行指定比例的随机抽样
7)union:RDD[A]+RDD[B]=>RDD[C]
8)distinct:RDD[A] =>RDD[B],RDD[B]的每个元素都是RDD[A]中的不重复元素
9)groupByKey/reduceByKey:RDD[A] =>RDD[B],执行groupByKey或者reduceByKey时相当于RDD[A]执行shuffle操作得到shuffledRDD(RDD[A]对应于Shuffled RDD而言是宽依赖,Shuffled RDD对应于RDD[B]而言是窄依赖),实际上都是根据key聚合
10)sortByKey:RDD[A]=>RDD[B],根据key逆序排(key,value),得到PairRDD
11)join:PairRDD[K,V1]+PairRDD[K,V2]=>PairRDD[K,[V1,V2]]
12)cogroup:PairRDD[K,V1]+PairRDD[K,V2]=>PairRDD[K,[Seq[V1],Seq[V2]]]
13)cartesian:RDD[A]+RDD[B]=>RDD[C],RDD[C]中的每个元素(key属于A,value属于B)
2.Action算子(立即执行)
1)reduce:RDD[A]=>result 汇聚RDD[A]中的所有元素得到计算结果
2)collect():RDD[A]=>Seq[B],以数组的方式返回RDD[A]中的所有元素
3)count:RDD[A]=>result,返回RDD[A]中元素个数
4)first:RDD[A]=>result,返回RDD[A]中的第一个元素
5)take:RDD[A]=>result,返回RDD[A]中的前n个元素
6)takeSample:RDD[A]=>result,首先执行sample,再执行take
7)saveAsTextFile:将数据集中的元素以textfile形式保存在本地文件系统
8)saveAsSequenceFile:将数据集中的元素以Hadoop Sequencefile形式保存在指定目录
9)countByKey:指定key的元素个数
10)foreach:对每个元素运行func
4.RDD容错
1)RDD缓存(cache/persist):RDD[A]=>RDD[B]=>RDD[C] 若RDD[C]部分Partitions丢失,则Spark会根据依赖关系找到上一个RDD[B](已缓存或者持久化),根据此RDD[B]中部分Partitions数据计算得到丢失Partitions,从而恢复RDD[C]
2)RDD检查点(checkpoint):计算完成后,重新建立Job计算并保存数据集至本地文件系统
5.Spark执行WordCount程序
val file=sc.textFile(“hdfs://localhost:9000/test.txt”)//HadoopRDD=>MapPartitionsRDD,(窄依赖)由于只关心每行数据,因此会执行map算子,解释了file是一个MapPartitionsRDD原因
val words=file.flatMap(line=>line.split(“”))//MapPartitionsRDD=>MapPartitionsRDD,(窄依赖)由于flatMap算子实际上还是Map算子,因此words仍然为MapPartitions
valpairs=words.map(word=>(word,1))//MapPartitionsRDD=>MapPartitionsRDD(窄依赖),pairs仍然为MapPartitionsRDD
val result=pairs.reduceByKey(_+_)//由于reduceByKey是一个shuffle算子,因此存在shuffle 的map端和reduce端
MapPartitionsRDD shuffledRDD MapPartitionsRDD
6.宽依赖和窄依赖
都是继承于抽象类Dependency[T]
abstract class Dependency[T] extendsSerializable{
def rdd:RDD[T] //rdd就是依赖的parent RDD
}
1)窄依赖(NarrowDependency)
abstract classNarrowDependency[T](_rdd:RDD[T]) extends Dependency[T]{
def getParents(partitionId:Int):Seq[Int] //返回partitionId依赖的Parent RDD的partitions
override def rdd:RDD[T]=_rdd //依赖对应的parentRDD
}
一对一的窄依赖OneToOneDependency
class OneToOneDependency[T](rdd:RDD[T])extends NarrowDependency[T](rdd){
override defgetParents(partitionId:Int)=List(partitionId) //子partition和父partition的Id相同
}
范围的窄依赖 RangeDependency
class RangeDependency[T](rdd:RDD[T])extends NarrowDependency[T](rdd){
override def getParents(partitionId:Int)={
if(partitionId>=outStart &&partitionId<outStart+length){
List(partitionId-outStart+inStart)//即如果子partitionId>=outStart(起始偏移)且partitionId<outStart+length(最大范围)
则其父partitionId=partitionId-outStart+inStart
}
}
}
2)宽依赖 ShuffleDependency
class ShuffleDependency[K,V,C](
@transient(临时缓存) _rdd:RDD[_<:Product2[K,V]], //临时缓存的rdd类型必须是Product2[K,V]子类型或者本类型
val partitioner : Partitioner,
val serializer:Option[Serializer]=None,
val keyOrdering:Option[Ordering[K]]=None,
valaggregator:Option[Aggregator[K,V,C]]=None,
val mapSideCombine:Boolean=false) //是否进行map端combine
extends Dependency[Product2[K,V]]{
override defrdd=_rdd.asInstanceOf[RDD[Product2[K,V]]]//创建新的RDD
val shuffleId:Int =_rdd.context.newShuffleId() //获得shuffleId
val shuffleHandle:ShuffleHandle =_rdd.context.env.shuffleManager.registerShuffle(
shuffleId,_rdd.partitions.size,this
)//创建shuffle管理器即由父RDD=>子RDD
_rdd.sparkContext.cleaner.foreach(_.registerShuffleForCleanup(this)) //清除shuffleRDD
}
)
Shuffle Manager包括两种即org.apache.spark.shuffle.hash.HashShuffleManager(基于Hash的ShuffleManager)和org.apache.spark.shuffle.sort.SortShuffleManager(基于排序的ShuffleManager)
7.Task简介
1)Task分类:org.apache.spark.scheduler.ShuffleMapTask
org.apache.spark.scheduler.ResultTask
2)Task执行入口:org.apache.spark.scheduler.Task#run调用ShuffleMapTask或者ResultTask的runTask=>runTask调用RDD的iterator
val task=new ShuffleMapTask/ReduceTask //创建task
task.runTask()=>iterator()返回RDD的迭代器
final defiterator(split:Partition,context:TaskContext):Iterator[T]={
If(storageLevel!=StorageLevel.NONE){//说明有缓存策略
SparkEnv.get.cacheManager.getOrCompute(this,split,context,storageLevel)//如果在缓存中则直接取数据集RDD,如果不在则需要根据依赖计算得到数据集RDD
}
else{
computeOrReadCheckpoint(split,context)//如果有checkpoint,直接从磁盘读取数据集RDD,否则需要计算
}
}
SparkEnv包含运行时的所有信息,包括cacheManager调用BlockManager管理缓存,除此之外还包括:
1)akka.actor.ActorSystem:异步并发模型(sparkDriver和sparkExecutor)
2)org.apache.spark.serializer.Serializer:序列化和反序列化工具
3)org.apache.spark.MapOutputTracker:保存Shuffle Map Task输出的位置信息,包括Driver端的MapOutputTrackerMaster和Executor端的MapOutputTrackerWorker
4)org.apache.spark.shuffle.ShuffleManager:Shuffle管理者,Driver端注册Shuffle信息,Executor端会上报并获取Shuffle信息
5)org.apache.spark.broadcast.BroadcastManager:广播变量的管理者
6)org.apache.spark.network.BlockTransferService:Executor读取Shuffle数据的Client,当前支持netty和nio
7)org.apache.spark.storage.BlockManager:管理Storage模块
8)org.apache.spark.SecurityManager:Spark认证授权模块
9)org.apache.spark.HttpFileSystem:提供HTTP服务的Server,主要用于Executor端下载依赖
10)org.apache.spark.shuffle.ShuffleMemoryManager:管理Shuffle过程使用的内存 ,采用的内存分配策略是对于N个Thread,每个Thread至少可以申请1/2*N的内存,最多申请1/N内存
用户在创建org.apache.spark.SparkContext时会创建org.apache.spark.SparkEnv
8.RDD缓存处理
核心方法:defgetOrCompute[T](
rdd:RDD[T],
partition:Partition,
context:TaskContext
storageLevel:StorageLevel):Iterator[T]={
valkey=RDDBlockId(rdd.id,partition.index)//BlockManager利用rdd_id和待计算的partition_id计算得到缓存中的BlockID
blockManager.get(key) match{
case Some(blockResult) => //缓存命中
读取缓存数据
}
case None=> //缓存未命中
需要重新计算
val computeValues=rdd.computeOrReadCheckpoint(partition,context) //从checkpoint检查点读取数据
}
)
9.checkpoint处理
1)创建checkpoint目录
valfs=path.getFileSystem(rdd.context.hadoopConfiguration) //返回HDFS分布式文件系统fs
fs.mkdir(path)
2)创建广播变量
val broadcastedConf=rdd.context.broadcast(
new SerializableWritable(rdd.context.hadoopConfiguration)
)
3) 启动新的Job进行计算,并将计算结果保存在path路径
rdd.context.runJob(rdd,CheckpointRDD.writeToFile[T](path.toString,broadcastedConf)_)
4)根据结果的路径path创建checkpointRDD
val newRDD=newCheckpointRDD[T](rdd.context,path.toString)
5)保存结果并清除原始RDD的依赖、partition信息等等
RDDCheckpointData.synchronized{
cpFile=Some(path.toString)
cpRDD=Some(newRDD)
rdd.markCheckpointed(newRDD) //清除原始RDD的依赖
cpState=Checkpointed //标记checkpoint状态为完成
}
每一个RDD都必须实现自己的compute接口