Spark Catalyst 源码分析

Architecture



Ø 把输入的SQL,parse成unresolved logical plan,这一步参考SqlParser的实现


Ø 把unresolved logical plan转化成resolved logical plan,这一步参考analysis的实现


Ø 把resolved logical plan转化成optimized logical plan,这一步参考optimize的实现


Ø 把optimized logical plan转化成physical plan,这一步参考QueryPlanner Strategy的实现


Source Code Module


Rule

Rule是一个抽象类,拥有一个名字,默认为类名。Rule的实现有很多,渗透在不同的处理过程(analyze, optimize)里。

RuleExecutor是规则执行类,下面两个实现会具体讲:
Analyzer
Optimizer

RuleExecutor 支持的策略:一次或多次。用来控制 converge结束的条件。这里的Strategy名字感觉有点误导人。

  1. /** 
  2.    * An execution strategy for rules that indicates the maximum number of executions. If the 
  3.    * execution reaches fix point (i.e. converge) before maxIterations, it will stop. 
  4.    */  
  5.   abstract class Strategy { def maxIterations: Int }  
  6.   
  7.   /** A strategy that only runs once. */  
  8.   case object Once extends Strategy { val maxIterations = 1 }  
  9.   
  10.   /** A strategy that runs until fix point or maxIterations times, whichever comes first. */  
  11.   case class FixedPoint(maxIterations: Int) extends Strategy  

RuleExecutor的Batch类和batches变量:
  1. /** A batch of rules. */  
  2.   protected case class Batch(name: String, strategy: Strategy, rules: Rule[TreeType]*)  
  3.   
  4.   /** Defines a sequence of rule batches, to be overridden by the implementation. */  
  5.   protected val batches: Seq[Batch]  
一个batch有多个Rule

batches在apply()里执行的时候,把一个plan丢进来后,用左折叠处理每个batch,最后吐出来一个plan。
converge的条件是达到最大策略次数或者两个TreeNode相等。apply()处理过程如下:
  1. /** 
  2.    * Executes the batches of rules defined by the subclass. The batches are executed serially 
  3.    * using the defined execution strategy. Within each batch, rules are also executed serially. 
  4.    */  
  5.   def apply(plan: TreeType): TreeType = {  
  6.     var curPlan = plan  
  7.   
  8.     batches.foreach { batch =>  
  9.       var iteration = 1   
  10.       var lastPlan = curPlan  
  11.       curPlan = batch.rules.foldLeft(curPlan) { case (plan, rule) => rule(plan) }  
  12.   
  13.       // Run until fix point (or the max number of iterations as specified in the strategy.  
  14.       while (iteration < batch.strategy.maxIterations && !curPlan.fastEquals(lastPlan)) {  
  15.         lastPlan = curPlan  
  16.         curPlan = batch.rules.foldLeft(curPlan) {  
  17.           case (plan, rule) =>  
  18.             val result = rule(plan)  
  19.             if (!result.fastEquals(plan)) {  
  20.               logger.debug(...)  
  21.             }  
  22.             result  
  23.         }  
  24.         iteration += 1  
  25.       }  
  26.     }  
  27.     curPlan  
  28.   }  

下面具体介绍RuleExecutor的实现。

Analyzer

Analyzer使用于对最初的unresolved logical plan转化成为logical plan。这部分的分析会涵盖整个analysis package。

作用是把未确定的属性和关系,通过Schema信息(来自于Catalog类)和方法注册类来确定下来,这个过程中有三步,第三步会包含许多次的迭代。

  1. /** 
  2.  * Provides a logical query plan analyzer, which translates [[UnresolvedAttribute]]s and 
  3.  * [[UnresolvedRelation]]s into fully typed objects using information in a schema [[Catalog]] and 
  4.  * a [[FunctionRegistry]]. 
  5.  */  
  6. class Analyzer(catalog: Catalog, registry: FunctionRegistry, caseSensitive: Boolean)  
  7.   extends RuleExecutor[LogicalPlan] with HiveTypeCoercion {  

首先,Catalog类是一个记录表信息的类,专门提供给Analyzer用。
  1. trait Catalog {  
  2.   def lookupRelation(  
  3.     databaseName: Option[String],  
  4.     tableName: String,  
  5.     alias: Option[String] = None): LogicalPlan  
  6.   
  7.   def registerTable(databaseName: Option[String], tableName: String, plan: LogicalPlan): Unit  
  8. }  

看一个SimpleCatalog的实现,该类在SQLContext里使用, 把表名和LogicalPlan存在HashMap里维护起来,生命周期随上下文。提供注册表、删除表、查找表的功能。
  1. class SimpleCatalog extends Catalog {  
  2.   val tables = new mutable.HashMap[String, LogicalPlan]()  
  3.   
  4.   def registerTable(databaseName: Option[String],tableName: String, plan: LogicalPlan): Unit = {  
  5.     tables += ((tableName, plan))  
  6.   }  
  7.   
  8.   def dropTable(tableName: String) = tables -= tableName  
  9.   
  10.   def lookupRelation(  
  11.       databaseName: Option[String],  
  12.       tableName: String,  
  13.       alias: Option[String] = None): LogicalPlan = {  
  14.     val table = tables.get(tableName).getOrElse(sys.error(s"Table Not Found: $tableName"))  
  15.   
  16.     // If an alias was specified by the lookup, wrap the plan in a subquery so that attributes are  
  17.     // properly qualified with this alias.  
  18.     alias.map(a => Subquery(a.toLowerCase, table)).getOrElse(table)  
  19.   }  
  20. }  

在查找的时候可以代入一个别名,会把他包装成一个Subquery。Subquery是个简单的case class。
  1. case class Subquery(alias: String, child: LogicalPlan) extends UnaryNode {  
  2.   def output = child.output.map(_.withQualifiers(alias :: Nil))  
  3.   def references = Set.empty  
  4. }  

FunctionRegistry类似于Catalog,记录的是函数,在hive package里,处理的是Hive的UDF
  1. trait FunctionRegistry {  
  2.   def lookupFunction(name: String, children: Seq[Expression]): Expression  
  3. }  

FunctionRegistry的实现在Catalyst里目前只有一个(在Hive模块里有实现,具体在最后一节Hive内),如下,如果你要查找方法,就会抛异常。
  1. /** 
  2.  * A trivial catalog that returns an error when a function is requested.  Used for testing when all 
  3.  * functions are already filled in and the analyser needs only to resolve attribute references. 
  4.  */  
  5. object EmptyFunctionRegistry extends FunctionRegistry {  
  6.   def lookupFunction(name: String, children: Seq[Expression]): Expression = {  
  7.     throw new UnsupportedOperationException  
  8.   }  
  9. }  

回到Analyzer,SQLContext在使用Analyzer前,这样生成:
  1. @transient  
  2. protected[sql] lazy val catalog: Catalog = new SimpleCatalog  
  3. protected[sql] lazy val analyzer: Analyzer =  
  4.     new Analyzer(catalog, EmptyFunctionRegistry, caseSensitive = true)  

接下来看Catalyst现在的Analyzer作为一个RuleExecutor,已经实现的功能:
  1. class Analyzer(catalog: Catalog, registry: FunctionRegistry, caseSensitive: Boolean)  
  2.   extends RuleExecutor[LogicalPlan] with HiveTypeCoercion {  
  3.   
  4.   // TODO: pass this in as a parameter.  
  5.   val fixedPoint = FixedPoint(100)  
  6.   
  7.   val batches: Seq[Batch] = Seq(  
  8.     Batch("MultiInstanceRelations", Once,  
  9.       NewRelationInstances),  
  10.     Batch("CaseInsensitiveAttributeReferences", Once,  
  11.       (if (caseSensitive) Nil else LowercaseAttributeReferences :: Nil) : _*),  
  12.     Batch("Resolution", fixedPoint,  
  13.       ResolveReferences ::  
  14.       ResolveRelations ::  
  15.       NewRelationInstances ::  
  16.       ImplicitGenerate ::  
  17.       StarExpansion ::  
  18.       ResolveFunctions ::  
  19.       GlobalAggregates ::  
  20.       typeCoercionRules :_*)  
  21.   )  

下面分别分析三个batch里面的Rule做的事情。

Batch One

首先是第一个batch里的NewRelationInstance这条Rule,他的作用就是避免一个逻辑计划上同一个实例出现多次,如果出现就生成一个新的plan,保证每个表达式id都唯一。

  1. /** 
  2.  * If any MultiInstanceRelation appears more than once in the query plan then the plan is updated so 
  3.  * that each instance has unique expression ids for the attributes produced. 
  4.  */  
  5. object NewRelationInstances extends Rule[LogicalPlan] {  
  6.   def apply(plan: LogicalPlan): LogicalPlan = {  
  7.     val localRelations = plan collect { case l: MultiInstanceRelation => l} // 这一步是搜集所有的多实例关系  
  8.     val multiAppearance = localRelations  
  9.       .groupBy(identity[MultiInstanceRelation])  
  10.       .filter { case (_, ls) => ls.size > 1 }  
  11.       .map(_._1)  
  12.       .toSet // 这一步是做过滤  
  13.   
  14.     plan transform { // 这一步是把原来plan里的多实例关系,凡是出现多个,就变成一个新的单一实例  
  15.       case l: MultiInstanceRelation if multiAppearance contains l => l.newInstance  
  16.     }  
  17.   }  
  18. }  

LogicalPlan本身是TreeNode的子类,TreeNode具备collect等一些scala collection操作的能力,这个例子里第一步搜集的过程中体现了collect能力。

TreeNode是Catalyst里的重要基础类,后面有小节会具体讲。

Batch Two

第二个batch是大小写相关的,如果对大小写不敏感,那么就执行LowercaseAttributeReferences这条Rule,会把所有的属性都变成小写

  1. /** 
  2.    * Makes attribute naming case insensitive by turning all UnresolvedAttributes to lowercase. 
  3.    */  
  4.   object LowercaseAttributeReferences extends Rule[LogicalPlan] {  
  5.     def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  6.       case UnresolvedRelation(databaseName, name, alias) => // 第一类:未确定的关系  
  7.         UnresolvedRelation(databaseName, name, alias.map(_.toLowerCase))  
  8.       case Subquery(alias, child) => Subquery(alias.toLowerCase, child) // 第二类:子查询  
  9.       case q: LogicalPlan => q transformExpressions { // 第三类: 其他类型  
  10.         case s: Star => s.copy(table = s.table.map(_.toLowerCase))  // 指的是 * 号  
  11.         case UnresolvedAttribute(name) => UnresolvedAttribute(name.toLowerCase) // 未确定的属性  
  12.         case Alias(c, name) => Alias(c, name.toLowerCase)() // 别名  
  13.       }  
  14.     }  
  15.   }  

transform,transformExpressions是TreeNode提供的方法,用于前序遍历树(pre-order)。

从这个处理可以看到logicalPlan里面包含的种类。后续Expression这一块具体还要展开介绍。

Alias的一点注释:

  1. /**  
  2.  * Used to assign a new name to a computation.  
  3.  * For example the SQL expression "1 + 1 AS a" could be represented as follows:  
  4.  *  Alias(Add(Literal(1), Literal(1), "a")()  
  5.  *  

Batch Three

Resulotion是第三类batch,定义的结束条件是循环100次。下面是我加的注释,大致介绍Rule的作用,并挑选几个Rule的实现介绍。

  1. Batch("Resolution", fixedPoint,  
  2.       ResolveReferences :: // 确定属性  
  3.       ResolveRelations :: // 确定关系(从catalog里)  
  4.       NewRelationInstances :: // 去掉同一个实例出现多次的情况  
  5.       ImplicitGenerate :: // 把包含Generator且只有一条的表达式转化成Generate操作  
  6.       StarExpansion :: // 扩张 *   
  7.       ResolveFunctions :: // 确定方法(从FunctionRegistry里)  
  8.       GlobalAggregates :: // 把包含Aggregate的表达式转化成Aggregate操作  
  9.       typeCoercionRules :_*) // 来自于HiveTypeCoercion,主要针对Hive语法做强制转换,包含多种规则  

用post-order遍历树,把未确定的属性确定下来。如果没有做成功,未确定的属性依然会留下来,留给下一次迭代的时候再确定。

  1. /** 
  2.    * Replaces [[UnresolvedAttribute]]s with concrete 
  3.    * [[expressions.AttributeReference AttributeReferences]] from a logical plan node's children. 
  4.    */  
  5.   object ResolveReferences extends Rule[LogicalPlan] {  
  6.     def apply(plan: LogicalPlan): LogicalPlan = plan transformUp {  
  7.       case q: LogicalPlan if q.childrenResolved =>  
  8.         logger.trace(s"Attempting to resolve ${q.simpleString}")  
  9.         q transformExpressions {  
  10.           case u @ UnresolvedAttribute(name) =>  
  11.             // Leave unchanged if resolution fails.  Hopefully will be resolved next round.  
  12.             val result = q.resolve(name).getOrElse(u)  
  13.             logger.debug(s"Resolving $u to $result")  
  14.             result  
  15.         }  
  16.     }  
  17.   }  

确定是通过LogicalPlan的resolve方法做的。这个具体在LogicalPlan里介绍,resolve方法是LogicalPlan的唯一且重要方法。


从catalog里查找关系

  1. /** 
  2.    * Replaces [[UnresolvedRelation]]s with concrete relations from the catalog. 
  3.    */  
  4.   object ResolveRelations extends Rule[LogicalPlan] {  
  5.     def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  6.       case UnresolvedRelation(databaseName, name, alias) =>  
  7.         catalog.lookupRelation(databaseName, name, alias)  
  8.     }  
  9.   }  

Generator是表达式的一种,根据一种inputrow产生0个或多个rows。

  1. /** 
  2.    * When a SELECT clause has only a single expression and that expression is a 
  3.    * [[catalyst.expressions.Generator Generator]] we convert the 
  4.    * [[catalyst.plans.logical.Project Project]] to a [[catalyst.plans.logical.Generate Generate]]. 
  5.    */  
  6.   object ImplicitGenerate extends Rule[LogicalPlan] {  
  7.     def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  8.       case Project(Seq(Alias(g: Generator, _)), child) =>  
  9.         Generate(g, join = false, outer = false, None, child)  
  10.     }  
  11.   }  

确定方法类似确定关系。

  1. /** 
  2.    * Replaces [[UnresolvedFunction]]s with concrete [[expressions.Expression Expressions]]. 
  3.    */  
  4.   object ResolveFunctions extends Rule[LogicalPlan] {  
  5.     def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  6.       case q: LogicalPlan =>  
  7.         q transformExpressions {  
  8.           case u @ UnresolvedFunction(name, children) if u.childrenResolved =>  
  9.             registry.lookupFunction(name, children)  
  10.         }  
  11.     }  
  12.   }  


换针对Hive语法做强制转换,规则如下

  1. trait HiveTypeCoercion {  
  2.   val typeCoercionRules = List(PropagateTypes, ConvertNaNs, WidenTypes, PromoteStrings, BooleanComparisons, BooleanCasts, StringToIntegralCasts, FunctionArgumentConversion)  

举个简单的例子来看下表达式的使用和替换:

  1. /** 
  2.    * Converts string "NaN"s that are in binary operators with a NaN-able types (Float / Double) * to the appropriate numeric equivalent. 
  3.    */  
  4.   object ConvertNaNs extends Rule[LogicalPlan] {  
  5.     val stringNaN = Literal("NaN", StringType)  
  6.   
  7.     def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  8.       case q: LogicalPlan => q transformExpressions {  
  9.         // Skip nodes who's children have not been resolved yet.  
  10.         case e if !e.childrenResolved => e  
  11.   
  12.         /* Double Conversions */  
  13.         case b: BinaryExpression if b.left == stringNaN && b.right.dataType == DoubleType =>  
  14.           b.makeCopy(Array(b.right, Literal(Double.NaN)))  
  15.         case b: BinaryExpression if b.left.dataType == DoubleType && b.right == stringNaN =>  
  16.           b.makeCopy(Array(Literal(Double.NaN), b.left))  
  17.         case b: BinaryExpression if b.left == stringNaN && b.right == stringNaN =>  
  18.           b.makeCopy(Array(Literal(Double.NaN), b.left))  
  19.   
  20.         /* Float Conversions */  
  21.         case b: BinaryExpression if b.left == stringNaN && b.right.dataType == FloatType =>  
  22.           b.makeCopy(Array(b.right, Literal(Float.NaN)))  
  23.         case b: BinaryExpression if b.left.dataType == FloatType && b.right == stringNaN =>  
  24.           b.makeCopy(Array(Literal(Float.NaN), b.left))  
  25.         case b: BinaryExpression if b.left == stringNaN && b.right == stringNaN =>  
  26.           b.makeCopy(Array(Literal(Float.NaN), b.left))  
  27.       }  
  28.     }  
  29.   }  

Optimizer

Optimizer用于把analyzedplan转化成为optimized plan。目前Catalyst的optimizer包下就这一个类,SQLContext也是直接使用的这个类。

同样,我们看一下里面包括了哪些处理过程:

  1. object Optimizer extends RuleExecutor[LogicalPlan] {  
  2.   val batches =  
  3.     Batch("Subqueries", Once,  
  4.       EliminateSubqueries) ::  
  5.     Batch("ConstantFolding", Once,  
  6.       ConstantFolding,  
  7.       BooleanSimplification,  
  8.       SimplifyCasts) ::  
  9.     Batch("Filter Pushdown", Once,  
  10.       EliminateSubqueries,  
  11.       CombineFilters,  
  12.       PushPredicateThroughProject,  
  13.       PushPredicateThroughInnerJoin) :: Nil  
  14. }  


Batch One

和子查询相关的一批规则,包含一条消除子查询的规则:EliminateSubqueries

  1. /** 
  2.  * Removes [[catalyst.plans.logical.Subquery Subquery]] operators from the plan.  Subqueries are 
  3.  * only required to provide scoping information for attributes and can be removed once analysis is 
  4.  * complete. 
  5.  */  
  6. object EliminateSubqueries extends Rule[LogicalPlan] {  
  7.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  8.     case Subquery(_, child) => child // 处理方式是凡是带child的,都用child替换自己  
  9.   }  
  10. }  

注释提到,过了analysis这一步之后,子查询就可以移除了。


Batch Two

第二批规则,常量折叠

  1. Batch("ConstantFolding", Once,  
  2.       ConstantFolding, // 常量折叠  
  3.       BooleanSimplification, // 提早短路掉布尔表达式  
  4.       SimplifyCasts) // 去掉多余的Cast操作  

具体看:
  1. /** 
  2.  * Replaces [[catalyst.expressions.Expression Expressions]] that can be statically evaluated with 
  3.  * equivalent [[catalyst.expressions.Literal Literal]] values. 
  4.  */  
  5. object ConstantFolding extends Rule[LogicalPlan] {  
  6.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  7.     case q: LogicalPlan => q transformExpressionsDown {  
  8.       // Skip redundant folding of literals.  
  9.       case l: Literal => l  
  10.       case e if e.foldable => Literal(e.apply(null), e.dataType)  
  11.     }  
  12.   }  
  13. }  

这里不得不提一下foldable字段在Expression类里的定义:

  1. /** 
  2.    * Returns true when an expression is a candidate for static evaluation before the query is 
  3.    * executed. 
  4.    * 
  5.    * The following conditions are used to determine suitability for constant folding: 
  6.    *  - A [[expressions.Coalesce Coalesce]] is foldable if all of its children are foldable 
  7.    *  - A [[expressions.BinaryExpression BinaryExpression]] is foldable if its both left and right 
  8.    *    child are foldable 
  9.    *  - A [[expressions.Not Not]], [[expressions.IsNull IsNull]], or 
  10.    *    [[expressions.IsNotNull IsNotNull]] is foldable if its child is foldable. 
  11.    *  - A [[expressions.Literal]] is foldable. 
  12.    *  - A [[expressions.Cast Cast]] or [[expressions.UnaryMinus UnaryMinus]] is foldable if its 
  13.    *    child is foldable. 
  14.    */  
  15.   // TODO: Supporting more foldable expressions. For example, deterministic Hive UDFs.  
  16.   def foldable: Boolean = false  
只有Literal表达式是foldable的,其余表达式必须表达式中每个元素都满足foldable。

第二种规则也好理解,简化布尔表达式。也就是早早地给表达式做一个短路判断。

  1. /** 
  2.  * Simplifies boolean expressions where the answer can be determined without evaluating both sides. 
  3.  * Note that this rule can eliminate expressions that might otherwise have been evaluated and thus 
  4.  * is only safe when evaluations of expressions does not result in side effects. 
  5.  */  
  6. object BooleanSimplification extends Rule[LogicalPlan] {  
  7.   def apply(plan: LogicalPlan): LogicalPlan = plan transform {  
  8.     case q: LogicalPlan => q transformExpressionsUp {  
  9.       case and @ And(left, right) =>  
  10.         (left, right) match {  
  11.           case (Literal(true, BooleanType), r) => r  
  12.           case (l, Literal(true, BooleanType)) => l  
  13.           case (Literal(false, BooleanType), _) => Literal(false)  
  14.           case (_, Literal(false, BooleanType)) => Literal(false)  
  15.           case (_, _) => and  
  16.         }  
  17.   
  18.       case or @ Or(left, right) =>  
  19.         (left, right) match {  
  20.           case (Literal(true, BooleanType), _) => Literal(true)  
  21.           case (_, Literal(true, BooleanType)) => Literal(true)  
  22.           case (Literal(false, BooleanType), r) => r  
  23.           case (l, Literal(false, BooleanType)) => l  
  24.           case (_, _) => or  
  25.         }  
  26.     }  
  27.   }  
  28. }  

把Cast操作全部移走。

  1. /** 
  2.  * Removes [[catalyst.expressions.Cast Casts]] that are unnecessary because the input is already 
  3.  * the correct type. 
  4.  */  
  5. object SimplifyCasts extends Rule[LogicalPlan] {  
  6.   def apply(plan: LogicalPlan): LogicalPlan = plan transformAllExpressions {  
  7.     case Cast(e, dataType) if e.dataType == dataType => e  
  8.   }  
  9. }  

Batch Three

一批 过滤下推 规则,

  1. Batch("Filter Pushdown", Once,  
  2.       EliminateSubqueries, // 消除子查询  
  3.       CombineFilters, // 过滤操作取合集  
  4.       PushPredicateThroughProject, // 为映射操作下推谓词  
  5.       PushPredicateThroughInnerJoin) // 为inner join下推谓词  

具体不一一列举了。

SQLContext

SQLContext的这一个RuleExecutor实现已经到了物理执行计划SparkPlan的处理了。也是一种实现,注册了自己的batch,如下:
  1. /** 
  2.    * Prepares a planned SparkPlan for execution by binding references to specific ordinals, and 
  3.    * inserting shuffle operations as needed. 
  4.    */  
  5.   @transient  
  6.   protected[sql] val prepareForExecution = new RuleExecutor[SparkPlan] {  
  7.     val batches =  
  8.       Batch("Add exchange", Once, AddExchange) ::  
  9.       Batch("Prepare Expressions", Once, new BindReferences[SparkPlan]) :: Nil  
  10.   }  


以上就是Rule包,及RuleExecutor在各处的实现。其中Analyze和Optimize是Catalyst目前提供的,SQL组件直接拿来使用。

TreeNode

TreeNode Library支持的三个特性:

    · Scala collection like methods (foreach, map, flatMap, collect, etc)

    · transform accepts a partial function that is used to generate a newtree.

    · debugging support pretty printing, easy splicing of trees, etc.

Collection操作能力

偏函数


继承结构


全局唯一id

  1. object TreeNode {  
  2.   private val currentId = new java.util.concurrent.atomic.AtomicLong  
  3.   protected def nextId() = currentId.getAndIncrement()  
  4. }  

几种节点

  1. /** 
  2.  * A [[TreeNode]] that has two children, [[left]] and [[right]]. 
  3.  */  
  4. trait BinaryNode[BaseType <: TreeNode[BaseType]] {  
  5.   def left: BaseType  
  6.   def right: BaseType  
  7.   
  8.   def children = Seq(left, right)  
  9. }  
  10.   
  11. /** 
  12.  * A [[TreeNode]] with no children. 
  13.  */  
  14. trait LeafNode[BaseType <: TreeNode[BaseType]] {  
  15.   def children = Nil  
  16. }  
  17.   
  18. /** 
  19.  * A [[TreeNode]] with a single [[child]]. 
  20.  */  
  21. trait UnaryNode[BaseType <: TreeNode[BaseType]] {  
  22.   def child: BaseType  
  23.   def children = child :: Nil  
  24. }  

每个node唯一id,导致在比较的时候,不同分支上长得一样结构的node也不相同,比较如下:

  1.   def sameInstance(other: TreeNode[_]): Boolean = {  
  2.     this.id == other.id  
  3.   }  
  4.   
  5.   def fastEquals(other: TreeNode[_]): Boolean = {  
  6.     sameInstance(other) || this == other  
  7.   }  
  8.   
  9. foreach的时候,先做自己,再把孩子们做一遍  
  10. def foreach(f: BaseType => Unit): Unit = {  
  11.     f(this)  
  12.     children.foreach(_.foreach(f))  
  13.   }  

map的时候是按前序对每个节点都做一次处理

  1. def map[A](f: BaseType => A): Seq[A] = {  
  2.     val ret = new collection.mutable.ArrayBuffer[A]()  
  3.     foreach(ret += f(_))  
  4.     ret  
  5.   }  

其他的很多变化都类似,接收的是函数或偏函数,把他们作用到匹配的节点上去执行

变化总共有这些,按类别分:

map, flatMap, collect,

mapChildren,  withNewChildren,

transform, transformDown, transformChildrenDown 前序

                    transformUp,  transformChildrenUp          后序

基本上就这些,其实就是提供对这棵树及其子节点的顺序遍历和处理能力


Plan

QueryPlan的继承结构



QueryPlan提供了三个东西,

Ø  其一是定义了output,是对外输出的一个属性序列

    def output:Seq[Attribute]


Ø  其二是借用TreeNode的那套transform方法,实现了一套transformExpression方法,用途是把partialfunction遍历到各个子节点上。

 

Ø  其三是一个expressions方法,返回Seq[expression],用于搜集本query里所有的表达式。

 

QueryPlanCatalyst里的实现是LogicalPlan,在SQL组件里的实现是SparkPlan,前者主要要被处理、分析和优化,后者是真正被处理执行的,下面简单介绍两者。


Logical Plan

在QueryPlan上增加的几个属性:

1.      references 用于生成output属性列表的参考属性列表

          def references: Set[Attribute]

 

2.      lazy val inputSet: Set[Attribute] = children.flatMap(_.output).toSet

 

3.      自己及children是否resolved

 

4.      resolve方法,重要,看起来费劲

  1. def resolve(name: String): Option[NamedExpression] = {  
  2.     val parts = name.split("\\.")  
  3.     // Collect all attributes that are output by this nodes children where either the first part  
  4.     // matches the name or where the first part matches the scope and the second part matches the  
  5.     // name.  Return these matches along with any remaining parts, which represent dotted access to  
  6.     // struct fields.  
  7.     val options = children.flatMap(_.output).flatMap { option =>  
  8.       // If the first part of the desired name matches a qualifier for this possible match, drop it.  
  9.       val remainingParts = if (option.qualifiers contains parts.head) parts.drop(1else parts  
  10.       if (option.name == remainingParts.head) (option, remainingParts.tail.toList) :: Nil else Nil  
  11.     }  
  12.   
  13.     options.distinct match {  
  14.       case (a, Nil) :: Nil => Some(a) // One match, no nested fields, use it.  
  15.       // One match, but we also need to extract the requested nested field.  
  16.       case (a, nestedFields) :: Nil =>  
  17.         a.dataType match {  
  18.           case StructType(fields) =>  
  19.             Some(Alias(nestedFields.foldLeft(a: Expression)(GetField), nestedFields.last)())  
  20.           case _ => None // Don't know how to resolve these field references  
  21.         }  
  22.       case Nil => None         // No matches.  
  23.       case ambiguousReferences =>  
  24.         throw new TreeNodeException(  
  25.           this, s"Ambiguous references to $name: ${ambiguousReferences.mkString(",")}")  
  26.     }  
  27.   }  

三种抽象子类:

  1. /** 
  2.  * A logical plan node with no children. 
  3.  */  
  4. abstract class LeafNode extends LogicalPlan with trees.LeafNode[LogicalPlan] {  
  5.   self: Product =>  
  6.   // Leaf nodes by definition cannot reference any input attributes.  
  7.   def references = Set.empty  
  8. }  
  9.   
  10. /** 
  11.  * A logical plan node with single child. 
  12.  */  
  13. abstract class UnaryNode extends LogicalPlan with trees.UnaryNode[LogicalPlan] {  
  14.   self: Product =>  
  15. }  
  16.   
  17. /** 
  18.  * A logical plan node with a left and right child. 
  19.  */  
  20. abstract class BinaryNode extends LogicalPlan with trees.BinaryNode[LogicalPlan] {  
  21.   self: Product =>  
  22. }  

分别看LogicalPlan的三种Node的实现结构:LeafNode,UnaryNode,BinaryNode


LeafNode

  1. /** 
  2.  * A logical node that represents a non-query command to be executed by the system.  For example, 
  3.  * commands can be used by parsers to represent DDL operations. 
  4.  */  
  5. abstract class Command extends LeafNode {  
  6.   self: Product =>   
  7.   def output = Seq.empty  
  8. }  
  9.   
  10. /** 
  11.  * Returned for commands supported by a given parser, but not catalyst.  In general these are DDL 
  12.  * commands that are passed directly to another system. 
  13.  */  
  14. case class NativeCommand(cmd: String) extends Command  
  15.   
  16. /** 
  17.  * Returned by a parser when the users only wants to see what query plan would be executed, without 
  18.  * actually performing the execution. 
  19.  */  
  20. case class ExplainCommand(plan: LogicalPlan) extends Command  
  21.   
  22. case object NoRelation extends LeafNode {  
  23.   def output = Nil  
  24. }  

对于Command和BaseRelation,在sql.hive包内有更多实现


MetastoreRelation的作用在Hive一节会说明。


Command略。

UnaryNode


BinaryNode


Spark Plan

SparkPlan类继承结构如下图:


在SQL模块的execution package的basicOperator类里,有许多SparkPlan的实现,包括

Project,Filter,Sample,Union,StopAfter,TopK,Sort,ExsitingRdd

 

这些实现和Catalyst的basicOperator类里有很多重了,区别在于,SparkPlanQueryPlan的实现,同logical plan不同的是,SparkPlan会被Spark实现的Strategy真正执行,所以SQL模块里的basicOperator内的这些caseclass,比Catalyst多了execute()方法

 

具体Spark策略的实现参考下一小节。


Planning


Query Planner

QueryPlanner的职责是把逻辑执行计划转化成为物理执行计划,具备一系列Strategy的实现



  1. abstract class QueryPlanner[PhysicalPlan <: TreeNode[PhysicalPlan]] {  
  2.   /** A list of execution strategies that can be used by the planner */  
  3.   def strategies: Seq[Strategy]  
  4.   
  5.   /** 
  6.    * Given a [[plans.logical.LogicalPlan LogicalPlan]], returns a list of `PhysicalPlan`s that can 
  7.    * be used for execution. If this strategy does not apply to the give logical operation then an 
  8.    * empty list should be returned. 
  9.    */  
  10.   abstract protected class Strategy extends Logging {  
  11.     def apply(plan: LogicalPlan): Seq[PhysicalPlan]  
  12.   }  
  13.   
  14.   /** 
  15.    * Returns a placeholder for a physical plan that executes `plan`. This placeholder will be 
  16.    * filled in automatically by the QueryPlanner using the other execution strategies that are 
  17.    * available. 
  18.    */  
  19.   protected def planLater(plan: LogicalPlan) = apply(plan).next()  
  20.   
  21.   def apply(plan: LogicalPlan): Iterator[PhysicalPlan] = {  
  22.     // Obviously a lot to do here still...  
  23.     val iter = strategies.view.flatMap(_(plan)).toIterator  
  24.     assert(iter.hasNext, s"No plan for $plan")  
  25.     iter  
  26.   }  
  27. }  

QueryPlanner impl

目前的实现是SparkStrategies

在SQLContext里的使用是SparkPlanner:

  1. protected[sql] class SparkPlanner extends SparkStrategies {  
  2.     val sparkContext = self.sparkContext  
  3.   
  4.     val strategies: Seq[Strategy] =  
  5.       TopK ::  
  6.       PartialAggregation ::  
  7.       SparkEquiInnerJoin ::  
  8.       BasicOperators ::  
  9.       CartesianProduct ::  
  10.       BroadcastNestedLoopJoin :: Nil  
  11.   }  

在HiveContext里的使用是带了hive策略的SparkPlanner:

  1. val hivePlanner = new SparkPlanner with HiveStrategies {  
  2.     val hiveContext = self  
  3.   
  4.     override val strategies: Seq[Strategy] = Seq(  
  5.       TopK,  
  6.       ColumnPrunings,  
  7.       PartitionPrunings,  
  8.       HiveTableScans,  
  9.       DataSinks,  
  10.       Scripts,  
  11.       PartialAggregation,  
  12.       SparkEquiInnerJoin,  
  13.       BasicOperators,  
  14.       CartesianProduct,  
  15.       BroadcastNestedLoopJoin  
  16.     )  
  17.   }  

Strategy & impl

Strategy的实现主要包含Spark Strategy和Hive Strategy。前者基本上对应了sql.execution包里的类。后者是在Spark策略的基础上附加的一些策略。

Expression

Expression几个属性:

1.      带DataType,并且自带一些inline方法帮助一些dataType的转换

2.      带reference,reference是Seq[Attribute],Attribute是NamedExpression子类。

3.      foldable ,即静态可以直接执行的表达式

Expression里只有Literal可折叠,Literal是LeafExpression,根据dataType生成不同类型表达式

  1. object Literal {  
  2.   def apply(v: Any): Literal = v match {  
  3.     case i: Int => Literal(i, IntegerType)  
  4.     case l: Long => Literal(l, LongType)  
  5.     case d: Double => Literal(d, DoubleType)  
  6.     case f: Float => Literal(f, FloatType)  
  7.     case b: Byte => Literal(b, ByteType)  
  8.     case s: Short => Literal(s, ShortType)  
  9.     case s: String => Literal(s, StringType)  
  10.     case b: Boolean => Literal(b, BooleanType)  
  11.     case null => Literal(null, NullType)  
  12.   }  
  13. }  
  14.   
  15. case class Literal(value: Any, dataType: DataType) extends LeafExpression {  
  16.   
  17.   override def foldable = true  
  18.   def nullable = value == null  
  19.   def references = Set.empty  
  20.   
  21.   override def toString = if (value != null) value.toString else "null"  
  22.   
  23.   type EvaluatedType = Any  
  24.   override def apply(input: Row):Any = value // 执行这个叶子表达式的话就是返回value值  
  25. }  

4.      resolved 具体关心children是否都resolved。

childeren是TreeNode里的概念,在TreeNode里是一个Seq[BaseType],而BaseType是TreeNode[T]里的范型。在Expression这里,即TreeNode[Expression],BaseType就是Expression。


Expression继承结构



抽象子类如下:

  1. abstract class BinaryExpression extends Expression with trees.BinaryNode[Expression] {  
  2.   self: Product =>  
  3.   def symbol: String  
  4.   override def foldable = left.foldable && right.foldable  
  5.   def references = left.references ++ right.references  
  6.   override def toString = s"($left $symbol $right)"  
  7. }  
  8.   
  9. abstract class LeafExpression extends Expression with trees.LeafNode[Expression] {  
  10.   self: Product =>  
  11. }  
  12.   
  13. abstract class UnaryExpression extends Expression with trees.UnaryNode[Expression] {  
  14.   self: Product =>  
  15.   def references = child.references  
  16. }  

Expression impl


SchemaRDD

SchemaRDD是一个RDD[Row],Row在Catalyst对应的是Table里的一行,定义是

  1. trait Row extends Seq[Any] with Serializable  

SchemaRDD就两部分实现,还有几个SQLContext的方法调用

一是RDD的Function的实现

  1. // =========================================================================================  
  2. // RDD functions: Copy the interal row representation so we present immutable data to users.  
  3. // =========================================================================================  
  4. override def compute(split: Partition, context: TaskContext): Iterator[Row] =  
  5.   firstParent[Row].compute(split, context).map(_.copy())  
  6.   
  7. override def getPartitions: Array[Partition] = firstParent[Row].partitions  
  8.   
  9. override protected def getDependencies: Seq[Dependency[_]] =  
  10.   List(new OneToOneDependency(queryExecution.toRdd))  // 该SchemaRDD与优化后的RDD是窄依赖  

二是DSL function的实现,如

  1. def select(exprs: NamedExpression*): SchemaRDD =  
  2.     new SchemaRDD(sqlContext, Project(exprs, logicalPlan))  

每次DSL的操作会转化成为新的SchemaRDD,

SchemaRDD的DSL操作与Catalyst组件提供的操作的对应关系为



DSL Operator的实现都依赖Catalyst的basicOperator,basicOperator里的操作都是LogicalPlan的继承类,主要分两类,一元UnaryNode和二元BinaryNode操作。而UnaryNode和BinaryNode都是TreeNode的实现,TreeNode里还有一种就是LeafNode。

basicOperator的各种实现都是caseclass,都是LogicalPlan,不具备execute能力




Hive


Hive Context

HiveContext是Spark SQL执行引擎之一,将hive数据结合到Spark环境中,读取的配置在hive-site.xml里指定。

继承关系



HiveContext里的sql parser使用的是HiveQl,

 

执行hql的时候,runHive方法接收cmd,且设置了最大返回行数

  1. protected def runHive(cmd: String, maxRows: Int = 1000): Seq[String]  

调用的方法是hive里的类,返回结果存在java的ArrayList里

 

错误日志会记录在outputBuffer里,用于打印输出


逻辑执行计划的几个步骤仍然类似SqlContext,因为QueryExecution也继承了过来

  1. abstract class QueryExecution extends super.QueryExecution {  

区别在于使用的实例不一样,且toRdd操作逻辑不一样


Hive Catalog


使用HiveMetastoreCatalog存表信息

 

HiveMetastoreCatalog内,通过HiveContext的hiveconf,创建了hiveclient,所以可以进行getTable,getPartition,createTable操作

 

HiveMetastoreCatalog内的MetastoreRelation,继承结构如下


通过hive的接口创建了Table,Partition,TableDesc,并带一个隐式转换HiveMetastoreTypes类,因为在把Schema里的Field转成Attribute的过程中,借助HiveMetastoreTypes的toDataType把Catalyst支持的DataType parse成hive支持的类型


Hive QL

参考HiveQl类

Hive UDF

  1. object HiveFunctionRegistry  
  2.   extends analysis.FunctionRegistry with HiveFunctionFactory with HiveInspectors {  

继承FunctionRegistry,实现的是lookupFunction方法


HiveFunctionFactory主要做反射的事情,以及把hive的类型转化成为catalyst type

包括

  1. def getFunctionInfo(name: String) = FunctionRegistry.getFunctionInfo(name)  
  2. def getFunctionClass(name: String) = getFunctionInfo(name).getFunctionClass  
  3. def createFunction[UDFType](name: String) =  
  4.   getFunctionClass(name).newInstance.asInstanceOf[UDFType]  

HiveInspectors是Catalyst DataType和Hive ObjectInspector的转化


Java类到Catalyst dataType的转化

  1. def javaClassToDataType(clz: Class[_]): DataType = clz match   

Hive Strategy

  1. val hivePlanner = new SparkPlanner with HiveStrategies {  
  2.     val hiveContext = self  
  3.   
  4.     override val strategies: Seq[Strategy] = Seq(  
  5.       TopK,  
  6.       ColumnPrunings,  
  7.       PartitionPrunings,  
  8.       HiveTableScans,  
  9.       DataSinks,  
  10.       Scripts,  
  11.       PartialAggregation,  
  12.       SparkEquiInnerJoin,  
  13.       BasicOperators,  
  14.       CartesianProduct,  
  15.       BroadcastNestedLoopJoin  
  16.     )  
  17.   }  


Summary

之前的那篇 Spark SQL组件源码分析走读了SQLContext的整个执行过程,有很多内容不够具体。本文结合Catalyst,做了更详细的说明。


全文完 :)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值