Spark LogicalPlan源码分析

Spark LogicalPlan 源码分析

原理

LogicalPlan是Apache Spark中的抽象类,用于表示Spark SQL中的逻辑执行计划。是Spark SQL查询优化和执行过程的核心组件之一。

LogicalPlan的由来机制主要基于以下两个原理:

  1. 解析器(Parser):在Spark SQL中,查询语句首先被解析器解析成一个抽象语法树(AST),这个AST表示了查询的语法结构。解析器将查询语句解析为一系列的语法规则,然后构建一个对应的AST。

  2. 转换器(Transformer):AST经过解析后,会进一步经过一系列的转换操作,将其转换为更高级别的逻辑计划。这些转换器将AST转换为LogicalPlan的实例,并且可以进行各种优化操作,例如谓词下推、投影消除等。

通过解析器和转换器的工作,LogicalPlan最终得到了一个完整的逻辑执行计划,该计划以树状结构表示查询的逻辑操作序列。每个节点代表一个具体的逻辑操作,例如选择(Selection)、投影(Projection)、连接(Join)等。节点之间通过父子关系连接,形成了一棵树。

LogicalPlan的由来机制充分利用了解析器和转换器的能力,将查询语句转换为可执行的逻辑计划,并提供了一种统一的方式来表示和操作这些计划。这使得Spark SQL可以对查询进行优化和执行,以提高查询性能和效率。

方法示例

在LogicalPlan类中,定义了以下方法:

  1. isStreaming: Boolean

    • 返回一个布尔值,指示该子树是否来自流式数据源。

    示例:

    val plan: LogicalPlan = ...
    val isStreaming: Boolean = plan.isStreaming
    
  2. maxRows: Option[Long]

    • 返回计划可能计算的最大行数。对于可以推迟Limit操作符的操作者(例如Union),应该重写此方法。

    示例:

    val plan: LogicalPlan = ...
    val maxRowCount: Option[Long] = plan.maxRows
    
  3. maxRowsPerPartition: Option[Long]

    • 返回计划在每个分区上可能计算的最大行数。

    示例:

    val plan: LogicalPlan = ...
    val maxRowCountPerPartition: Option[Long] = plan.maxRowsPerPartition
    
  4. resolved: Boolean

    • 返回一个布尔值,指示该表达式及其所有子表达式是否已解析为具体模式。

    示例:

    val plan: LogicalPlan = ...
    val isResolved: Boolean = plan.resolved
    
  5. childrenResolved: Boolean

    • 返回一个布尔值,指示该查询计划的所有子计划是否已解析。

    示例:

    val plan: LogicalPlan = ...
    val areChildrenResolved: Boolean = plan.childrenResolved
    
  6. resolve(schema: StructType, resolver: Resolver): Seq[Attribute]

    • 将给定的模式解析为该查询计划中的具体Attribute引用。

    示例:

    val plan: LogicalPlan = ...
    val schema: StructType = ...
    val resolvedAttributes: Seq[Attribute] = plan.resolve(schema, resolver)
    
  7. resolveChildren(nameParts: Seq[String], resolver: Resolver): Option[NamedExpression]

    • 根据所有子节点的输入,将给定的字符串转换为NamedExpression。

    示例:

    val plan: LogicalPlan = ...
    val nameParts: Seq[String] = ...
    val resolvedExpression: Option[NamedExpression] = plan.resolveChildren(nameParts, resolver)
    
  8. resolve(nameParts: Seq[String], resolver: Resolver): Option[NamedExpression]

    • 根据此逻辑计划的输出,将给定的字符串转换为NamedExpression。

    示例:

    val plan: LogicalPlan = ...
    val nameParts: Seq[String] = ...
    val resolvedExpression: Option[NamedExpression] = plan.resolve(nameParts, resolver)
    
  9. resolveQuoted(name: String, resolver: Resolver): Option[NamedExpression]

    • 将给定的属性名称按点号拆分为多个部分,并根据output中的属性进行解析。

    示例:

    val plan: LogicalPlan = ...
    val name: String = ...
    val resolvedExpression: Option[NamedExpression] = plan.resolveQuoted(name, resolver)
    
  10. refresh(): Unit

    • 刷新(或使无效)递归计划中缓存的任何元数据/数据。

    示例:

    val plan: LogicalPlan = ...
    plan.refresh()
    
  11. outputOrdering: Seq[SortOrder]

    • 返回该计划生成的输出排序。

    示例:

    val plan: LogicalPlan = ...
    val ordering: Seq[SortOrder] = plan.outputOrdering
    

这些方法用于对逻辑计划进行操作、解析和查询。你可以根据具体需求调用这些方法来获取计划的相关信息,解析模式,判断计划是否已解析,刷新缓存等。

中文源码

abstract class LogicalPlan
  extends QueryPlan[LogicalPlan]
  with AnalysisHelper
  with LogicalPlanStats
  with QueryPlanConstraints
  with Logging {

  /** 
   * 返回true,如果该子树包含来自流式数据源的数据。
   */
  def isStreaming: Boolean = children.exists(_.isStreaming == true)

  override def verboseStringWithSuffix: String = {
    super.verboseString + statsCache.map(", " + _.toString).getOrElse("")
  }

  /**
   * 返回该计划可能计算的最大行数。
   *
   * 可以推迟Limit操作符的操作者应重写此函数(例如Union)。
   * 可以通过Limit操作进行推进的任何操作者都应重写此函数(例如Project)。
   */
  def maxRows: Option[Long] = None

  /**
   * 返回该计划在每个分区上可能计算的最大行数。
   */
  def maxRowsPerPartition: Option[Long] = maxRows

  /**
   * 如果该表达式及其所有子表达式已解析为具体模式,则返回true;
   * 如果它仍然包含任何未解析的占位符,则返回false。
   * LogicalPlan的实现可以重写此方法(例如UnresolvedRelation应返回`false`)。
   */
  lazy val resolved: Boolean = expressions.forall(_.resolved) && childrenResolved

  override protected def statePrefix = if (!resolved) "'" else super.statePrefix

  /**
   * 返回true,如果该查询计划的所有子计划都已解析。
   */
  def childrenResolved: Boolean = children.forall(_.resolved)

  /**
   * 将给定的模式解析为该查询计划中的具体[[Attribute]]引用。由于未解析的[[Attribute]],
   * 此函数只能在经过分析的计划上调用,否则将抛出[[AnalysisException]]。
   */
  def resolve(schema: StructType, resolver: Resolver): Seq[Attribute] = {
    schema.map { field =>
      resolve(field.name :: Nil, resolver).map {
        case a: AttributeReference => a
        case _ => sys.error(s"can not handle nested schema yet...  plan $this")
      }.getOrElse {
        throw new AnalysisException(
          s"Unable to resolve ${field.name} given [${output.map(_.name).mkString(", ")}]")
      }
    }
  }

  private[this] lazy val childAttributes = AttributeSeq(children.flatMap(_.output))

  private[this] lazy val outputAttributes = AttributeSeq(output)

  /**
   * 根据所有子节点的输入,将给定的字符串转换为[[NamedExpression]]。属性以以下形式的字符串表示:
   * `[scope].AttributeName.[nested].[fields]...`。
   */
  def resolveChildren(
      nameParts: Seq[String],
      resolver: Resolver): Option[NamedExpression] =
    childAttributes.resolve(nameParts, resolver)

  /**
   * 根据此逻辑计划的输出,将给定的字符串转换为[[NamedExpression]]。属性以以下形式的字符串表示:
   * `[scope].AttributeName.[nested].[fields]...`。
   */
  def resolve(
      nameParts: Seq[String],
      resolver: Resolver): Option[NamedExpression] =
    outputAttributes.resolve(nameParts, resolver)

  /**
   * 将给定的属性名称按点号拆分为多个部分,但不要拆分由反引号引起来的名称,
   * 例如`ab.cd`.`efg`应该拆分为两个部分 "ab.cd" 和 "efg"。
   */
  def resolveQuoted(
      name: String,
      resolver: Resolver): Option[NamedExpression] = {
    outputAttributes.resolve(UnresolvedAttribute.parseAttributeName(name), resolver)
  }

  /**
   * 刷新(或使无效)递归计划中缓存的任何元数据/数据。
   */
  def refresh(): Unit = children.foreach(_.refresh())

  /**
   * 返回该计划生成的输出排序。
   */
  def outputOrdering: Seq[SortOrder] = Nil
}

/**
 * 没有子节点的逻辑计划节点。
 */
abstract class LeafNode extends LogicalPlan {
  override final def children: Seq[LogicalPlan] = Nil
  override def producedAttributes: AttributeSet = outputSet

  /** 可以在分析过程中幸存下来的叶子节点必须定义自己的统计信息。*/
  def computeStats(): Statistics = throw new UnsupportedOperationException
}

/**
 * 具有单个子节点的逻辑计划节点。
 */
abstract class UnaryNode extends LogicalPlan {
  def child: LogicalPlan

  override final def children: Seq[LogicalPlan] = child :: Nil

  /**
   * 通过替换原始约束表达式中的相应别名,生成一组附加的别名约束
   */
  protected def getAliasedConstraints(projectList: Seq[NamedExpression]): Set[Expression] = {
    var allConstraints = child.constraints.asInstanceOf[Set[Expression]]
    projectList.foreach {
      case a @ Alias(l: Literal, _) =>
        allConstraints += EqualNullSafe(a.toAttribute, l)
      case a @ Alias(e, _) =>
        // 对于`projectList`中的每个别名,将约束中的引用替换为其属性。
        allConstraints ++= allConstraints.map(_ transform {
          case expr: Expression if expr.semanticEquals(e) =>
            a.toAttribute
        })
        allConstraints += EqualNullSafe(e, a.toAttribute)
      case _ => // 不做任何改变。
    }

    allConstraints -- child.constraints
  }

  override protected def validConstraints: Set[Expression] = child.constraints
}

/**
 * 具有左子节点和右子节点的逻辑计划节点。
 */
abstract class BinaryNode extends LogicalPlan {
  def left: LogicalPlan
  def right: LogicalPlan

  override final def children: Seq[LogicalPlan] = Seq(left, right)
}

abstract class OrderPreservingUnaryNode extends UnaryNode {
  override final def outputOrdering: Seq[SortOrder] = child.outputOrdering
}

源码分析

该源码是Scala中的逻辑计划(LogicalPlan)相关类的定义和实现。逻辑计划用于表示Spark SQL查询的逻辑结构,包括数据源、操作符、表达式等。以下是对源码的分析总结:

  1. 抽象类LogicalPlan是所有逻辑计划节点的基类,它继承了多个特质(trait),包括QueryPlan、AnalysisHelper、LogicalPlanStats、QueryPlanConstraints和Logging。

  2. LogicalPlan中定义了一系列方法和属性,用于描述和操作逻辑计划节点。例如,isStreaming方法用于判断该子树是否来自流式数据源;maxRows方法用于返回计划可能计算的最大行数;resolved属性用于判断计划是否已解析等。

  3. LogicalPlan还定义了一些辅助方法,如resolve方法用于解析模式(schema)到具体的Attribute引用;refresh方法用于刷新缓存的元数据/数据;outputOrdering方法用于返回计划生成的输出排序等。

  4. LeafNode是没有子节点的逻辑计划节点的抽象类,继承自LogicalPlan。LeafNode必须定义自己的统计信息,并且没有子节点。

  5. UnaryNode是具有单个子节点的逻辑计划节点的抽象类,继承自LogicalPlan。UnaryNode定义了一个名为child的抽象方法,并提供了相应的实现。UnaryNode还定义了getAliasedConstraints方法,用于生成一组附加的别名约束。

  6. BinaryNode是具有左子节点和右子节点的逻辑计划节点的抽象类,继承自LogicalPlan。BinaryNode定义了left和right两个抽象方法,并提供了相应的实现。

  7. OrderPreservingUnaryNode是保持顺序的一元逻辑计划节点的抽象类,继承自UnaryNode。OrderPreservingUnaryNode通过重写outputOrdering方法来保持输入和输出之间的排序顺序。

总的来说,该源码定义了逻辑计划相关的类和特质,提供了对逻辑计划的描述、操作和解析功能。它为Spark SQL查询的构建和优化提供了基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值