Spark SQL 源码分析之Physical Plan 到 RDD的具体实现

本文深入探讨Spark SQL Physical Plan的实现,包括Project、Filter、Sample、Union、Limit、TakeOrdered、Sort等操作的详细执行过程。重点分析了HashJoin,如LeftSemiJoinHash、BroadcastHashJoin和ShuffleHashJoin的内部工作机制,揭示了Spark SQL如何通过RDD进行高效的数据处理和JOIN操作。
摘要由CSDN通过智能技术生成

 接上一篇文章Spark SQL Catalyst源码分析之Physical Plan,本文将介绍Physical Plan的toRDD的具体实现细节:

  我们都知道一段sql,真正的执行是当你调用它的collect()方法才会执行Spark Job,最后计算得到RDD。

  lazy val toRdd: RDD[Row] = executedPlan.execute()

  Spark Plan基本包含4种操作类型,即BasicOperator基本类型,还有就是Join、Aggregate和Sort这种稍复杂的。

  如图:

  

一、BasicOperator

1.1、Project

  Project 的大致含义是:传入一系列表达式Seq[NamedExpression],给定输入的Row,经过Convert(Expression的计算eval)操作,生成一个新的Row。

  Project的实现是调用其child.execute()方法,然后调用mapPartitions对每一个Partition进行操作。
  这个f函数其实是new了一个MutableProjection,然后循环的对每个partition进行Convert。

 
  1. case class Project(projectList: Seq[NamedExpression], child: SparkPlan) extends UnaryNode {

  2. override def output = projectList.map(_.toAttribute)

  3. override def execute() = child.execute().mapPartitions { iter => //对每个分区进行f映射

  4. @transient val reusableProjection = new MutableProjection(projectList)

  5. iter.map(reusableProjection)

  6. }

  7. }

  通过观察MutableProjection的定义,可以发现,就是bind references to a schema 和 eval的过程:

  将一个Row转换为另一个已经定义好schema column的Row。
  如果输入的Row已经有Schema了,则传入的Seq[Expression]也会bound到当前的Schema。

 
  1. case class MutableProjection(expressions: Seq[Expression]) extends (Row => Row) {

  2. def this(expressions: Seq[Expression], inputSchema: Seq[Attribute]) =

  3. this(expressions.map(BindReferences.bindReference(_, inputSchema))) //bound schema

  4.  
  5. private[this] val exprArray = expressions.toArray

  6. private[this] val mutableRow = new GenericMutableRow(exprArray.size) //新的Row

  7. def currentValue: Row = mutableRow

  8. def apply(input: Row): Row = {

  9. var i = 0

  10. while (i < exprArray.length) {

  11. mutableRow(i) = exprArray(i).eval(input) //根据输入的input,即一个Row,计算生成的Row

  12. i += 1

  13. }

  14. mutableRow //返回新的Row

  15. }

  16. }

1.2、Filter

 Filter的具体实现是传入的condition进行对input row的eval计算,最后返回的是一个Boolean类型,

 如果表达式计算成功,返回true,则这个分区的这条数据就会保存下来,否则会过滤掉。

 
  1. case class Filter(condition: Expression, child: SparkPlan) extends UnaryNode {

  2. override def output = child.output

  3.  
  4. override def execute() = child.execute().mapPartitions { iter =>

  5. iter.filter(condition.eval(_).asInstanceOf[Boolean]) //计算表达式 eval(input row)

  6. }

  7. }

1.3、Sample

  Sample取样操作其实是调用了child.execute()的结果后,返回的是一个RDD,对这个RDD调用其sample函数,原生方法。

 
  1. case class Sample(fraction: Double, withReplacement: Boolean, seed: Long, child: SparkPlan)

  2. extends UnaryNode

  3. {

  4. override def output = child.output

  5.  
  6. // TODO: How to pick seed?

  7. override def execute() = child.execute().sample(withReplacement, fraction, seed)

  8. }

1.4、Union

  Union操作支持多个子查询的Union,所以传入的child是一个Seq[SparkPlan]

  execute()方法的实现是对其所有的children,每一个进行execute(),即select查询的结果集合RDD。

  通过调用SparkContext的union方法,将所有子查询的结果合并起来。

 
  1. case class Union(children: Seq[SparkPlan])(@transient sqlContext: SQLContext) extends SparkPlan {

  2. // TODO: attributes output by union should be distinct for nullability purposes

  3. override def output = children.head.output

  4. override def execute() = sqlContext.sparkContext.union(children.map(_.execute())) //子查询的结果进行union

  5.  
  6. override def otherCopyArgs = sqlContext :: Nil

  7. }

1.5、Limit

  Limit操作在RDD的原生API里也有,即take().

  但是Limit的实现分2种情况:

  第一种是 limit作为结尾的操作符,即select

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值