这周关注了一下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的细节是怎样的。
(本文完)