RDD实现详解

来源:互联网 发布:企业免费记账软件 编辑:程序博客网 时间:2024/06/07 19:01

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接口