spark sql和catalyst实例结合源码分析

9 篇文章 0 订阅
4 篇文章 0 订阅

这周关注了一下spark sql和catalyst,看了一下相关资料和各种社区内的动态,特此总结:

spark sql和catalyst为什么会出现?这要从shark说起,shark在spark生态中相当于hadoop生态中的hive,但是shark为了实现和hive的兼容,使用了hql解析,逻辑查询计划的翻译和优化生成物理查询计划。hive是将物理执行计划转化为了MR作业,但是shark则是将物理执行计划转化为了RDD操作。所以spark团队引入了spark sql和catalyst,尽量使spark上的sql功能独立于hive。

spark sql和catalyst做了什么?现在的spark sql版本从hive解析之后生成抽象语法树开始就接管了hive的工作。优化生成物理查询计划则有catalyst完成。spark sql同时还提供了精简的sql parser以及一套scala dsl,因此用户可以完全脱离hive。

下面我们看一下spark sql文档中给出的一个实例,我们结合源码分析一下这个实例的每条语句,实例如下

      val sqlc=new org.apache.spark.sql.SQLContext(sc)
      import sqlc._
      case class Person(name:String,age:Int)
      val people=sc.textFile("../examples/src/main/resources/people.txt").map(_.split(",")).map(p=>Person(p(0),p(1).trim.toInt))
      people.registerAsTable("people")
      val teenagers=sql("select name from people where age>=13 and age<=19")
      teenagers.map(t=>"name: "+t(0)).collect().foreach(println)

val sqlc=new org.apache.spark.sql.SQLContext(sc)

首先由sc构造sql的上下文。

case class Person(name:String,age:Int)

新建一个case类,定义person的schema信息,可知person的schema信息是string的名字和int的年纪。

val people=sc.textFile("../examples/src/main/resources/people.txt").map(_.split(",")).map(p=>Person(p(0),p(1).trim.toInt))

然后将马上要进行的sql查询使用的数据的每个记录都构造一个person实例(在此每个记录都会构造person信息是不是很浪费空间?)。

people.registerAsTable("people")

上面这条语句涉及到一个隐式转换,由于people是MappedRDD,因此没有registerAsTable这个方法,但是在sql上下文类中定义了一个如下方法:

  /**
   * Creates a SchemaRDD from an RDD of case classes.
   * 隐式类型转换,当一个RDD调用SchemaRDD中的方法的时候,如果RDD中没有那个方法,就经过隐式类型转化
   * 比如:
   * val people=sc.textFile("../examples/src/main/resources/people.txt").map(_.split(",")).map(p=>Person(p(0),p(1).trim.toInt))
   * people.registerAsTable("people")
   * 在第一句话结束之后,people是MappedRdd,但是第二句话是SchemaRDD中的方法,
   * 所以隐式类型转换可以将people转化为SchemaRDD,ExistingRdd.fromProductRdd(rdd)是叶子节点 
   * @group userf
   */
  implicit def createSchemaRDD[A <: Product: TypeTag](rdd: RDD[A]) =
    new SchemaRDD(this, SparkLogicalPlan(ExistingRdd.fromProductRdd(rdd)))
这个方法将people进行了隐式转换为SchemaRDD,然后调用registerAsTable将table信息存储在catalog中,SchemaRDD还有spark中自己定义的dsl。可以通过SchemaRDD实例直接调用一些类似sql语句中关键字的函数,比如"schemaRDD.select('a, 'b + 'c, 'd as 'aliasedName)"
val teenagers=sql("select name from people where age>=13 and age<=19")

在此利用sql上下文类中的sql方法,获得的是执行一条sql返回的记录集合的RDD,仔细分析一下非常重要的sql方法:

  /**
   * Executes a SQL query using Spark, returning the result as a SchemaRDD.
   * 执行一条sql语句,返回的是这条sql语句执行出来的结果
   * @group userf
   */
  def sql(sqlText: String): SchemaRDD = {
    /*
     * 输入的是sqlText的一个string,最终返回的是一个RDD,例如:
     * val teenagers=sql("select name from people where age>=13 and age<=19")
     * parseSql(sqlText)返回的是逻辑执行计划
     * */
    val result = new SchemaRDD(this, parseSql(sqlText))
    
    // We force query optimization to happen right away instead of letting it happen lazily like
    // when using the query DSL.  This is so DDL commands behave as expected.  This is only
    // generates the RDD lineage for DML queries, but do not perform any execution.
    /*
     * toRdd是最后执行的触发器,返回的result是收集到的结果,也是一个RDD,但是这个RDD是row的集合
     * */
    result.queryExecution.toRdd
    result
  }

这里触发执行的是.toRdd,先看result.queryExecution,其会通过sql上下文调用executePlan,输入是逻辑执行计划:

  /* 关键函数:
   * 输入的是逻辑执行计划,这一步物理查询计划已经生成好,就差最后执行toRDD方法(但是不是这样的,
   * QueryExecution中都是lazy变量,这些只是在最后调用toRDD方法的时候才会执行),
   * toRDD方法就是最后执行的触发器,
   * */
  protected[sql] def executePlan(plan: LogicalPlan): this.QueryExecution =
    //QueryExecution是一个抽象类
    new this.QueryExecution { val logical = plan }

在这个函数中new this.QueryExecution会让QueryExecution类中的很多语句都执行,但是由于它们都用lazy申明,所以new this.QueryExecution其实没有执行,最后toRdd方法执行的时候才触发这些语句的真实执行,申明为lazy的语句涉及到的类大多在catalyst模块中,包括分析逻辑执行计划,逻辑执行计划的优化,最后策略的选择和物理执行计划的生成,toRdd只是调用了物理执行计划的execute函数,使sql语句“执行”。

    lazy val analyzed = analyzer(logical)
    lazy val optimizedPlan = optimizer(analyzed)
    // TODO: Don't just pick the first one...
    lazy val sparkPlan = planner(optimizedPlan).next()
    lazy val executedPlan: SparkPlan = prepareForExecution(sparkPlan)
       
    /** Internal version of the RDD. Avoids copies and has no schema */
    lazy val toRdd: RDD[Row] = executedPlan.execute()

以上的语句将在以后分析catalyst中分析,至此有几点不明白的地方,是接下来需要分析的:
1,数据在此是通过ExistingRdd输入的,在注册table的隐式转换中,直接将ExistingRdd作为了查询计划的叶子节点,但是table scan操作在哪里,因为这个例子是没有scan操作的,数据都在person的实例集合中。

2,怎样解析sql语句成逻辑执行计划,这里主要看sqlparser文件。

3,在catalyst模块中涉及到的analyzer,optimizer和planner的细节是怎样的。

(本文完)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值