Spark SQL 物理执行计划各操作实现


http://blog.csdn.net/pelick/article/details/22748841

SparkStrategy: logical to physical

Catalyst作为一个实现无关的查询优化框架,在优化后的逻辑执行计划到真正的物理执行计划这部分只提供了接口,没有提供像Analyzer和Optimizer那样的实现。

本文介绍的是Spark SQL组件各个物理执行计划的操作实现。把优化后的逻辑执行计划映射到物理执行操作类这部分由SparkStrategies类实现,内部基于Catalyst提供的Strategy接口,实现了一些策略,用于分辨logicalPlan子类并替换为合适的SparkPlan子类。


SparkPlan继承体系如下。接下里会具体介绍其子类的实现。



SparkPlan

主要三部分:LeafNode、UnaryNode、BinaryNode

各自的实现类:



提供四个需要子类重载的方法

[java]  view plain  copy
  1. // TODO: Move to `DistributedPlan`  
  2. /** Specifies how data is partitioned across different nodes in the cluster. */  
  3. def outputPartitioning: Partitioning = UnknownPartitioning(0// TODO: WRONG WIDTH!  
  4. /** Specifies any partition requirements on the input data for this operator. */  
  5. def requiredChildDistribution: Seq[Distribution] =  
  6.   Seq.fill(children.size)(UnspecifiedDistribution)  
  7.   
  8. def execute(): RDD[Row]  
  9. def executeCollect(): Array[Row] = execute().collect()  

Distribution和Partitioning类用于表示数据分布情况。有以下几类,可以望文生义。


LeafNode


ExistingRdd

先介绍下Row和GenericRow的概念。

Row是一行output对应的数据,提供getXXX(i: Int)方法

[java]  view plain  copy
  1. trait Row extends Seq[Any] with Serializable  

支持数据类型包括Int, Long, Double, Float, Boolean, Short, Byte, String。支持按序数(ordinal)读取某一个列的值。读取前需要做isNullAt(i: Int)的判断。

对应的有一个MutableRow类,提供setXXX(i: Int, value: Any)方法。可以修改(set)某序数上的值


GenericRow是Row的一种方便实现,存的是一个数组

[java]  view plain  copy
  1. class GenericRow(protected[catalyst] val values: Array[Any]) extends Row  

所以对应的取值操作和判断是否为空操作会转化为数组上的定位取值操作。

它也有一个对应的GenericMutableRow类,可以修改(set)值。


ExistingRdd用于把绑定了case class的rdd的数据,转变为RDD[Row],同时反射提取出case class的属性(output)。转化过程的单例类和伴生对象如下:

[java]  view plain  copy
  1. object ExistingRdd {  
  2.   def convertToCatalyst(a: Any): Any = a match {  
  3.     case s: Seq[Any] => s.map(convertToCatalyst)  
  4.     case p: Product => new GenericRow(p.productIterator.map(convertToCatalyst).toArray)  
  5.     case other => other  
  6.   }  
  7.   // 把RDD[A]映射成为RDD[Row],map A中每一行数据  
  8.   def productToRowRdd[A <: Product](data: RDD[A]): RDD[Row] = {  
  9.     // TODO: Reuse the row, don't use map on the product iterator.  Maybe code gen?  
  10.     data.map(r => new GenericRow(r.productIterator.map(convertToCatalyst).toArray): Row)  
  11.   }  
  12.   
  13.   def fromProductRdd[A <: Product : TypeTag](productRdd: RDD[A]) = {  
  14.     ExistingRdd(ScalaReflection.attributesFor[A], productToRowRdd(productRdd))  
  15.   }  
  16. }  
  17.   
  18. case class ExistingRdd(output: Seq[Attribute], rdd: RDD[Row]) extends LeafNode {  
  19.   def execute() = rdd  
  20. }  

UnaryNode


Aggregate

隐式转换声明,针对本地分区的RDD,扩充了一些操作

[java]  view plain  copy
  1. /* Implicit conversions */  
  2. import org.apache.spark.rdd.PartitionLocalRDDFunctions._  

Groups input data by`groupingExpressions` and computes the `aggregateExpressions` for each group.

@param child theinput data source.

[java]  view plain  copy
  1. case class Aggregate(  
  2.     partial: Boolean,  
  3.     groupingExpressions: Seq[Expression],  
  4.     aggregateExpressions: Seq[NamedExpression],  
  5.     child: SparkPlan)(@transient sc: SparkContext)  

在初始化的时候,partial这个参数用来标志本次Aggregate操作只在本地做,还是要去到符合groupExpression的其他partition上都做。该判断逻辑如下:

[java]  view plain  copy
  1. override def requiredChildDistribution =  
  2.     if (partial) { // true, 未知的分布  
  3.       UnspecifiedDistribution :: Nil  
  4. else {  
  5.   // 如果为空,则分布情况是全部的tuple在一个single partition里  
  6.       if (groupingExpressions == Nil) {   
  7.         AllTuples :: Nil  
  8.       // 否则是集群分布的,分布规则来自groupExpressions  
  9.       } else {  
  10.         ClusteredDistribution(groupingExpressions) :: Nil  
  11.       }  
  12.     }  

最重要的execute()方法:
[java]  view plain  copy
  1. def execute() = attachTree(this"execute") {  
  2.   // 这里进行了一次隐式转换,生成了PartitionLocalRDDFunctions  
  3.   val grouped = child.execute().mapPartitions { iter =>  
  4.     val buildGrouping = new Projection(groupingExpressions)  
  5.     iter.map(row => (buildGrouping(row), row.copy()))  
  6.   }.groupByKeyLocally()  // 这里生成的结果是RDD[(K, Seq[V])]  
  7.   
  8.   val result = grouped.map { case (group, rows) =>  
  9. // 这一步会把aggregateExpressions对应到具体的spark方法都找出来  
  10. // 具体做法是遍历aggregateExpressions,各自newInstance  
  11.     val aggImplementations = createAggregateImplementations()  
  12.   
  13.     // Pull out all the functions so we can feed each row into them.  
  14.     val aggFunctions = aggImplementations.flatMap(_ collect { case f: AggregateFunction => f })  
  15.   
  16.     rows.foreach { row =>  
  17.       aggFunctions.foreach(_.update(row))  
  18.     }  
  19.     buildRow(aggImplementations.map(_.apply(group)))  
  20.   }  
  21.   
  22.   // TODO: THIS BREAKS PIPELINING, DOUBLE COMPUTES THE ANSWER, AND USES TOO MUCH MEMORY...  
  23.   if (groupingExpressions.isEmpty && result.count == 0) {  
  24.     // When there is no output to the Aggregate operator, we still output an empty row.  
  25.     val aggImplementations = createAggregateImplementations()  
  26.     sc.makeRDD(buildRow(aggImplementations.map(_.apply(null))) :: Nil)  
  27.   } else {  
  28.     result  
  29.   }  
  30. }  

AggregateExpression继承体系如下,这部分代码在Catalyst expressions包的aggregates.scala里:


他的第一类实现AggregateFunction,带一个update(input: Row)操作。子类的update操作是实际对Row执行变化。


DebugNode

DebugNode是把传进来child SparkPlan调用execute()执行,然后把结果childRdd逐个输出查看

[java]  view plain  copy
  1. case class DebugNode(child: SparkPlan) extends UnaryNode  

Exchange

[java]  view plain  copy
  1. case class Exchange(newPartitioning: Partitioning, child: SparkPlan) extends UnaryNode  

为某个SparkPlan,实施新的分区策略。
execute()方法:
[java]  view plain  copy
  1. def execute() = attachTree(this , "execute") {  
  2.     newPartitioning match {  
  3.       case HashPartitioning(expressions, numPartitions) =>  
  4.         // 把expression作用到rdd每个partition的每个row上  
  5.         val rdd = child.execute().mapPartitions { iter =>  
  6.           val hashExpressions = new MutableProjection(expressions)  
  7.           val mutablePair = new MutablePair[Row, Row]() // 相当于Tuple2  
  8.           iter.map(r => mutablePair.update(hashExpressions(r), r))  
  9.         }  
  10.         val part = new HashPartitioner(numPartitions)  
  11.         // 生成ShuffledRDD  
  12.         val shuffled = new ShuffledRDD[Row, Row, MutablePair[Row, Row]](rdd, part)  
  13.         shuffled.setSerializer(new SparkSqlSerializer(new SparkConf(false)))  
  14.         shuffled.map(_._2) // 输出Tuple2里的第二个值  
  15.   
  16.       case RangePartitioning(sortingExpressions, numPartitions) =>  
  17.         // TODO: RangePartitioner should take an Ordering.  
  18.         implicit val ordering = new RowOrdering(sortingExpressions)  
  19.   
  20.         val rdd = child.execute().mapPartitions { iter =>  
  21.           val mutablePair = new MutablePair[Row, Null](nullnull)  
  22.           iter.map(row => mutablePair.update(row, null))  
  23.         }  
  24.         val part = new RangePartitioner(numPartitions, rdd, ascending = true)  
  25.         val shuffled = new ShuffledRDD[Row, Null, MutablePair[Row, Null]](rdd, part)  
  26.         shuffled.setSerializer(new SparkSqlSerializer(new SparkConf(false)))  
  27.         shuffled.map(_._1)  
  28.   
  29.       case SinglePartition =>  
  30.         child.execute().coalesce(1, shuffle = true)  
  31.   
  32.       case _ => sys.error(s"Exchange not implemented for $newPartitioning")  
  33.       // TODO: Handle BroadcastPartitioning.  
  34.     }  
  35.   }  

Filter

[java]  view plain  copy
  1. case class Filter(condition: Expression, child: SparkPlan) extends UnaryNode  
  2.   
  3. def execute() = child.execute().mapPartitions { iter =>  
  4.   iter.filter(condition.apply(_).asInstanceOf[Boolean])  
  5. }  

Generate

[java]  view plain  copy
  1. case class Generate(  
  2.     generator: Generator,  
  3.     join: Boolean,  
  4.     outer: Boolean,  
  5.     child: SparkPlan)  
  6.   extends UnaryNode  

首先,Generator是表达式的子类,继承结构如下


Generator的作用是把input的row处理后输出0个或多个rows,makeOutput()的策略由子类实现。

Explode类做法是将输入的input array里的每一个value(可能是ArrayType,可能是MapType),变成一个GenericRow(Array(v)),输出就是一个


回到Generate操作,

join布尔值用于指定最后输出的结果是否要和输入的原tuple显示做join

outer布尔值只有在join为true的时候才生效,且outer为true的时候,每个input的row都至少会被作为一次output


总体上,Generate操作类似FP里的flatMap操作

[java]  view plain  copy
  1. def execute() = {  
  2.   if (join) {  
  3.     child.execute().mapPartitions { iter =>  
  4.       val nullValues = Seq.fill(generator.output.size)(Literal(null))  
  5.       // Used to produce rows with no matches when outer = true.  
  6.       val outerProjection =  
  7.         new Projection(child.output ++ nullValues, child.output)  
  8.   
  9.       val joinProjection =  
  10.         new Projection(child.output ++ generator.output, child.output ++ generator.output)  
  11.       val joinedRow = new JoinedRow  
  12.   
  13.       iter.flatMap {row =>  
  14.         val outputRows = generator(row)  
  15.         if (outer && outputRows.isEmpty) {  
  16.           outerProjection(row) :: Nil  
  17.         } else {  
  18.           outputRows.map(or => joinProjection(joinedRow(row, or)))  
  19.         }  
  20.       }  
  21.     }  
  22.   } else {  
  23.     child.execute().mapPartitions(iter => iter.flatMap(generator))  
  24.   }  
  25. }  

Project

[java]  view plain  copy
  1. case class Project(projectList: Seq[NamedExpression], child: SparkPlan) extends UnaryNode  

project的执行:

[java]  view plain  copy
  1. def execute() = child.execute().mapPartitions { iter =>  
  2.   @transient val reusableProjection = new MutableProjection(projectList)  
  3.   iter.map(reusableProjection)  
  4. }  

MutableProjection类是Row => Row的继承类,它构造的时候接收一个Seq[Expression],还允许接收一个inputSchema: Seq[Attribute]。MutableProjection用于根据表达式(和Schema,如果有Schema的话)把Row映射成新的Row,改变内部的column。


Sample

[java]  view plain  copy
  1. case class Sample(fraction: Double, withReplacement: Boolean, seed: Int, child: SparkPlan)  extends UnaryNode  
  2.   
  3. def execute() = child.execute().sample(withReplacement, fraction, seed)  

RDD的sample操作:

[java]  view plain  copy
  1. def sample(withReplacement: Boolean, fraction: Double, seed: Int): RDD[T] = {  
  2.   require(fraction >= 0.0"Invalid fraction value: " + fraction)  
  3.   if (withReplacement) {  
  4.     new PartitionwiseSampledRDD[T, T](thisnew PoissonSampler[T](fraction), seed)  
  5.   } else {  
  6.     new PartitionwiseSampledRDD[T, T](thisnew BernoulliSampler[T](fraction), seed)  
  7.   }  
  8. }  

生成的PartitionwiseSampledRDD会在RDD的每个partition都选取样本

PossionSampler和BernoulliSampler是RandomSampler的两种实现。


Sort

[java]  view plain  copy
  1. case class Sort(  
  2.     sortOrder: Seq[SortOrder],  
  3.     global: Boolean,  
  4.     child: SparkPlan)  
  5.   extends UnaryNode  

对分布有要求:

[java]  view plain  copy
  1. override def requiredChildDistribution =  
  2.   if (global) OrderedDistribution(sortOrder) :: Nil   
  3. else UnspecifiedDistribution :: Nil  

SortOrder类是UnaryExpression的实现,定义了tuple排序的策略(递增或递减)。该类只是为child expression们声明了排序策略。之所以继承Expression,是为了能影响到子树。

[java]  view plain  copy
  1. case class SortOrder(child: Expression, direction: SortDirection) extends UnaryExpression  

[java]  view plain  copy
  1. // RowOrdering继承Ordering[Row]  
  2. @transient  
  3.   lazy val ordering = new RowOrdering(sortOrder)  
  4.   
  5.   def execute() = attachTree(this"sort") {  
  6.     // TODO: Optimize sorting operation?  
  7.     child.execute()  
  8.       .mapPartitions(iterator => iterator.map(_.copy()).toArray.sorted(ordering).iterator,  
  9.         preservesPartitioning = true)  
  10.   }  

有一次隐式转换过程,.sorted是array自带的一个方法,因为ordering是RowOrdering类,该类继承Ordering[T],是scala.math.Ordering[T]类。


StopAfter

[java]  view plain  copy
  1. case class StopAfter(limit: Int, child: SparkPlan)(@transient sc: SparkContext) extends UnaryNode  

StopAfter实质上是一次limit操作

[java]  view plain  copy
  1. override def executeCollect() = child.execute().map(_.copy()).take(limit)  
  2. def execute() = sc.makeRDD(executeCollect(), 1// 设置并行度为1  

makeRDD实质上调用的是new ParallelCollectionRDD[T]的操作,此处的seq为tabke()返回的Array[T],而numSlices为1:

[java]  view plain  copy
  1. /** Distribute a local Scala collection to form an RDD. */  
  2.   def parallelize[T: ClassTag](seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = {  
  3.     new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())  
  4.   }  

TopK

[java]  view plain  copy
  1. case class TopK(limit: Int, sortOrder: Seq[SortOrder], child: SparkPlan)  
  2. (@transient sc: SparkContext) extends UnaryNode  

可以把TopK理解为类似Sort和StopAfter的结合,

[java]  view plain  copy
  1. @transient  
  2. lazy val ordering = new RowOrdering(sortOrder)  
  3.   
  4. override def executeCollect() = child.execute().map(_.copy()).takeOrdered(limit)(ordering)  
  5. def execute() = sc.makeRDD(executeCollect(), 1)  

takeOrdered(num)(sorting)实际触发的是RDD的top()()操作
[java]  view plain  copy
  1. def top(num: Int)(implicit ord: Ordering[T]): Array[T] = {  
  2.    mapPartitions { items =>  
  3.      val queue = new BoundedPriorityQueue[T](num)  
  4.      queue ++= items  
  5.      Iterator.single(queue)  
  6.    }.reduce { (queue1, queue2) =>  
  7.      queue1 ++= queue2  
  8.      queue1  
  9.    }.toArray.sorted(ord.reverse)  
  10.  }  

BoundedPriorityQueue是Spark util包里的一个数据结构,包装了PriorityQueue,他的优化点在于限制了优先队列的大小,比如在添加元素的时候,如果超出size了,就会进行对堆进行比较和替换。适合TopK的场景。

所以每个partition在排序前,只会产生一个num大小的BPQ(最后只需要选Top num个),合并之后才做真正的排序,最后选出前num个。


BinaryNode


BroadcastNestedLoopJoin

[java]  view plain  copy
  1. case class BroadcastNestedLoopJoin(  
  2.     streamed: SparkPlan, broadcast: SparkPlan, joinType: JoinType, condition: Option[Expression])  
  3.     (@transient sc: SparkContext)  
  4.   extends BinaryNode  

比较复杂的一次join操作,操作如下,
[java]  view plain  copy
  1. def execute() = {  
  2.   // 先将需要广播的SparkPlan执行后进行一次broadcast操作  
  3.   val broadcastedRelation =   
  4.   sc.broadcast(broadcast.execute().map(_.copy()).collect().toIndexedSeq)  
  5.   
  6.   val streamedPlusMatches = streamed.execute().mapPartitions { streamedIter =>  
  7.     val matchedRows = new mutable.ArrayBuffer[Row]  
  8.     val includedBroadcastTuples =    
  9.       new mutable.BitSet(broadcastedRelation.value.size)  
  10.     val joinedRow = new JoinedRow  
  11.       
  12.     streamedIter.foreach { streamedRow =>  
  13.       var i = 0  
  14.       var matched = false  
  15.   
  16.       while (i < broadcastedRelation.value.size) {  
  17.         // TODO: One bitset per partition instead of per row.  
  18.         val broadcastedRow = broadcastedRelation.value(i)  
  19.         if (boundCondition(joinedRow(streamedRow, broadcastedRow)).asInstanceOf[Boolean]) {  
  20.           matchedRows += buildRow(streamedRow ++ broadcastedRow)  
  21.           matched = true  
  22.           includedBroadcastTuples += i  
  23.         }  
  24.         i += 1  
  25.       }  
  26.   
  27.       if (!matched && (joinType == LeftOuter || joinType == FullOuter)) {  
  28.         matchedRows += buildRow(streamedRow ++ Array.fill(right.output.size)(null))  
  29.       }  
  30.     }  
  31.     Iterator((matchedRows, includedBroadcastTuples))  
  32.   }  
  33.   
  34.   val includedBroadcastTuples = streamedPlusMatches.map(_._2)  
  35.   val allIncludedBroadcastTuples =  
  36.     if (includedBroadcastTuples.count == 0) {  
  37.       new scala.collection.mutable.BitSet(broadcastedRelation.value.size)  
  38.     } else {  
  39.       streamedPlusMatches.map(_._2).reduce(_ ++ _)  
  40.     }  
  41.   
  42.   val rightOuterMatches: Seq[Row] =  
  43.     if (joinType == RightOuter || joinType == FullOuter) {  
  44.       broadcastedRelation.value.zipWithIndex.filter {  
  45.         case (row, i) => !allIncludedBroadcastTuples.contains(i)  
  46.       }.map {  
  47.         // TODO: Use projection.  
  48.         case (row, _) => buildRow(Vector.fill(left.output.size)(null) ++ row)  
  49.       }  
  50.     } else {  
  51.       Vector()  
  52.     }  
  53.   
  54.   // TODO: Breaks lineage.  
  55.   sc.union(  
  56.     streamedPlusMatches.flatMap(_._1), sc.makeRDD(rightOuterMatches))  
  57. }  

CartesianProduct

[java]  view plain  copy
  1. case class CartesianProduct(left: SparkPlan, right: SparkPlan) extends BinaryNode  

调用的是RDD的笛卡尔积操作,

[java]  view plain  copy
  1. def execute() =   
  2.   left.execute().map(_.copy()).cartesian(right.execute().map(_.copy())).map {  
  3.     case (l: Row, r: Row) => buildRow(l ++ r)  
  4.   }  

SparkEquiInnerJoin

[java]  view plain  copy
  1. case class SparkEquiInnerJoin(  
  2.     leftKeys: Seq[Expression],  
  3.     rightKeys: Seq[Expression],  
  4.     left: SparkPlan,  
  5.     right: SparkPlan) extends BinaryNode  

该join操作适用于left和right两部分partition一样大且提供各自keys的情况。

基本上看代码就可以了,没有什么可以说明的,做local join的时候借助的是PartitionLocalRDDFunctions里的方法。

[java]  view plain  copy
  1. def execute() = attachTree(this"execute") {  
  2.   val leftWithKeys = left.execute().mapPartitions { iter =>  
  3.     val generateLeftKeys = new Projection(leftKeys, left.output) // 传入了Schema  
  4.     iter.map(row => (generateLeftKeys(row), row.copy()))  
  5.   }  
  6.   
  7.   val rightWithKeys = right.execute().mapPartitions { iter =>  
  8.     val generateRightKeys = new Projection(rightKeys, right.output)  
  9.     iter.map(row => (generateRightKeys(row), row.copy()))  
  10.   }  
  11.   
  12.   // Do the join.  
  13.   // joinLocally是PartitionLocalRDDFunctions的方法  
  14.   val joined = filterNulls(leftWithKeys).joinLocally(filterNulls(rightWithKeys))  
  15.   // Drop join keys and merge input tuples.  
  16.   joined.map { case (_, (leftTuple, rightTuple)) => buildRow(leftTuple ++ rightTuple) }  
  17. }  
  18.   
  19. /** 
  20.  * Filters any rows where the any of the join keys is null, ensuring three-valued 
  21.  * logic for the equi-join conditions. 
  22.  */  
  23. protected def filterNulls(rdd: RDD[(Row, Row)]) =  
  24.   rdd.filter {  
  25.     case (key: Seq[_], _) => !key.exists(_ == null)  
  26.   }  

PartitionLocalRDDFunctions方法如下,该操作并不引入shuffle操作。两个RDD的partition数目需要相等。

[java]  view plain  copy
  1. def joinLocally[W](other: RDD[(K, W)]): RDD[(K, (V, W))] = {  
  2.   cogroupLocally(other).flatMapValues {  
  3.     case (vs, ws) => for (v <- vs.iterator; w <- ws.iterator) yield (v, w)  
  4.   }  
  5. }  

Other

Union

该操作直接继承SparkPlan

[java]  view plain  copy
  1. case class Union(children: Seq[SparkPlan])(@transient sc: SparkContext) extends SparkPlan  

用传入的SparkPlan集合各自的RDD执行结果生成一个UnionRDD

[java]  view plain  copy
  1. def execute() = sc.union(children.map(_.execute()))  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值