Spark中RDD DAG图的建立

Spark中RDD DAG图的建立

原创  2015年11月05日 17:48:58
  • 2523

   RDD是spark计算的核心,是分布式数据元素的集合,具有不可变、可分区、可被并行操作的特性,基础的RDD类包含了常用的操作,如果需要特殊操作可以继承RDD基类进行自己的扩展,基础预算包括map、filter、reduce等。
  RDD包含5个主要特性:partition、针对split的算子、自身依赖哪些RDD、分区类型(默认hash)、split计算是的分区位置(例如计算HDFS block的时候,让计算分配在block所在机器会减少网络数据的传输)
 如果需要自定义一个RDD,通常需要实现如下方法:

[java]  view plain  copy
  1. //对一个partition做何种运算  
  2. def compute(split: Partition, context: TaskContext): Iterator[T]  
  3.   
  4. //获得该RDD的所有分区  
  5. protected def getPartitions: Array[Partition]  
  6.   
  7. //获得该RDD有哪些依赖  
  8. protected def getDependencies: Seq[Dependency[_]] = deps  
  9.   
  10. //获得partition的最佳位置  
  11. protected def getPreferredLocations(split: Partition): Seq[String] = Nil  
  12.   
  13. //使用何种分区  
  14. @transient val partitioner: Option[Partitioner] = None  

    在一个作业提交给dagscheduler前,要先构建RDD的DAG图,构建过程需要建立依赖关系,在spark中有宽依赖和窄依赖之分,下面我们通过一个具体的计算链条来分析这个过程,代码如下:
[java]  view plain  copy
  1. val count = sc.parallelize(1 to 52).map((_,1)).reduceByKey(_+_).collect().foreach(println)  
   这段逻辑非常简单,构建1到5的数字集合,然后做一个wordcount操作,最后打印结果。
 执行过程从左到右,首先执行parallelize操作:
[java]  view plain  copy
  1. def parallelize[T: ClassTag](seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = {  
  2.   assertNotStopped()  
  3.   new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())  
  4. }  
   返回的是一个ParalleCollectionRDD的RDD,这里只是构建一个RDD的对象,还并未运行任何计算,继续往下看会发现在action触发前,也就是job submit之前是不进行任何计算的,直到job提交后才会进行,这就是RDD的延迟计算。后面进行map操作最终返回MapPartitionsRDD
[java]  view plain  copy
  1. def map[U: ClassTag](f: T => U): RDD[U] = {  
  2.   val cleanF = sc.clean(f)  
  3.   new MapPartitionsRDD[U, T](this, (context, pid, iter) => iter.map(cleanF))  
  4. }  
   注意上面代码的this,是上一个RDD,也就是ParallelCollectionRDD进入MapPartitionsRDD的构造函数可以看出,这个RDD其实是当前RDD的parent RDD,代码如下:
[java]  view plain  copy
  1. private[spark] class MapPartitionsRDD[U: ClassTag, T: ClassTag](  
  2.     prev: RDD[T],  
  3.     f: (TaskContext, Int, Iterator[T]) => Iterator[U],  // (TaskContext, partition index, iterator)  
  4.     preservesPartitioning: Boolean = false)  
  5.   extends RDD[U](prev) {  
  6.    
  7.   override val partitioner = if (preservesPartitioning) firstParent[T].partitioner else None  
  8.    
  9.   override def getPartitions: Array[Partition] = firstParent[T].partitions  
  10.    
  11.   override def compute(split: Partition, context: TaskContext) =  
  12.     f(context, split.index, firstParent[T].iterator(split, context))  
  13. }  
这样RDD之间的依赖关系就构建好了,进行reducebykey操作前会进行隐式转换,将RDD转化为PairRDDFunctions,然后进行reduceByKey操作。
    collect是一个action算子,上述map、reduce操作属于transform操作,只负责对RDD打转化标记,并不执行真正的计算。而action操作会导致作业提交至集群,由调度器做好切分stage后进行调度。
[java]  view plain  copy
  1. def collect(): Array[T] = {  
  2.   val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)  
  3.   Array.concat(results: _*)  
  4. }  
   runJob会触发作业的提交,客户端通过向AKKA服务器发送提交作业的消息,来提交作业,代码如下:
[java]  view plain  copy
  1. def submitJob[T, U](  
  2.     rdd: RDD[T],  
  3.     func: (TaskContext, Iterator[T]) => U,  
  4.     partitions: Seq[Int],  
  5.     callSite: CallSite,  
  6.     allowLocal: Boolean,  
  7.     resultHandler: (Int, U) => Unit,  
  8.     properties: Properties): JobWaiter[U] = {  
  9.   // Check to make sure we are not launching a task on a partition that does not exist.  
  10.   val maxPartitions = rdd.partitions.length  
  11.   partitions.find(p => p >= maxPartitions || p < 0).foreach { p =>  
  12.     throw new IllegalArgumentException(  
  13.       "Attempting to access a non-existent partition: " + p + ". " +  
  14.         "Total number of partitions: " + maxPartitions)  
  15.   }  
  16.    
  17.   val jobId = nextJobId.getAndIncrement()  
  18.   if (partitions.size == 0) {  
  19.     return new JobWaiter[U](this, jobId, 0, resultHandler)  
  20.   }  
  21.    
  22.   assert(partitions.size > 0)  
  23.   val func2 = func.asInstanceOf[(TaskContext, Iterator[_]) => _]  
  24.   val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)  
  25.   eventProcessLoop.post(JobSubmitted(  
  26.     jobId, rdd, func2, partitions.toArray, allowLocal, callSite, waiter, properties))  
  27.   waiter  
  28. }  
作业提交的消息中包含作业ID、最终的RDD,partitions等主要信息,注意这里的RDD是最终的RDD,因为通过这个RDD可以反推出所有依赖的RDD,所以提交时只要这一个就够了,后面我们会分析作业提交服务端是如何处理这个RDD的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值