小记--------sparksql执行全过程
1 案例 2 def main(args: Array[String]): Unit = { 3 4 // 1.创建sparkconf 5 val conf = new SparkConf() 6 .setMaster("local") 7 .setAppName("test-sql") 8 9 10 // 2.创建sparksession 11 val session: SparkSession = SparkSession 12 .builder() 13 .config(conf) 14 .getOrCreate() 15 16 17 // 3.创建数据表并读取数据 , 并创建了student的数据表(视图) 18 // 读取本地student.json 文件。 19 //{"id": 1 , "name" : "Kate" , "age" : 29} 20 //{"id": 2 , "name" : "Andy" , "age" : 39} 21 //{"id": 3 , "name" : "Tony" , "age" : 10} 22 session 23 .read 24 .json("D:\\daima\\work\\1011\\spark-test-zhonghuashishan\\src\\test\\file\\student.json") 25 .createOrReplaceTempView("student") 26 27 28 // SQL查询 29 session.sql("select name from student where age > 18 ").show() 30 }
一般来讲,对于sparkSQL系统,从SQL到spark中的RDD的执行需要经过两个大的阶段、
逻辑计划(LogicalPlan)
物理计划(PhysicalPlan)
SQL执行过程概览
逻辑计划阶段
会将用户所写的SQL语句转换成树型数据结构(逻辑算子树),SQL语句中蕴含的逻辑映射到逻辑算子树的不同节点,
逻辑计划阶段生成的逻辑算子树并不会直接提交执行,仅作为中间阶段。
逻辑算子树的生成过程经历3个子阶段
1.未解析的逻辑算子树;仅仅是数据结构,不包含任何数据信息等
2.解析后的逻辑算子树;节点中绑定各种信息
3.优化后的逻辑算子树;应用各种优化规则对一些低效的逻辑计划进行转换
物理计划阶段
将上一步逻辑计划阶段生成的逻辑算子树进行进一步转换,生成物理算子树。
物理算子树的节点会直接生成RDD或对RDD进行transformation操作(注:每个物理计划节点中都实现了对RDD进行转换的execute方法)
物理计划阶段的3个子阶段
1.物理算子树的列表;(注:同样的逻辑算子树可能对应多个物理算子树)
2.最优物理算子树;从算子树列表中按照一定的策略选取最优的物理算子树,然后对选取的物理算子树进行提交前的准备工作;例如:确保分区操作正确,物理算子树节点重用,执行代码生成等
3.准备后的物理算子树;对物理算子树生成的RDD执行action操作,即可提交程序
SQL语句的解析一直到提交之前,整个转换过程都在spark集群的Driver端进行不涉及分布式环境。
Catalyst
sparkSQL内部实现流程中平台无关部分的基础框架称为Catalyst,它主要包括InternalRow体系、TreeNode体系和Expression体系。
InternalRow体系
spark SQL 内部实现中,InternalRow就是用来表示一行行数据的类,物理算子树节点产生和转换的RDD类型即为RDD[InternalRow] 。 InternalRow中的每一列都是Catalyst内部定义的数据类型。
InternalRow作为一个抽象类,包含numFields和update方法,以及各列数据对应的get与set方法,InternalRow中都是根据下表来访问和操作列元素的。
其具体的实现包括BaseGenericInternalRow、UnsafeRow和JoinedRow3个直接子类
InternalRow体系
BaseGenericInternalRow:同样是一个抽象类,实现了InternalRow中定义的所有get类型方法,这些方法的实现都通过调用类中定义的genericGet虚函数进行,该函数的实现在下一级子类中(也就是GenericInternalRow 、 SpecificInternalRow 、 MutbaleUnsafeInternalRow类中)
JoinedRow:该类主要用于Join操作,将两个InternalRow放在一起形成新的InternalRow。使用时需要注意构造参数的顺序。
UnsafeRow:不采用java对象存储的方式,避免了JVM中垃圾回收(GC)的代价。此外UnsafeRow对行数据进行了特定的编码,使得存储更加高效。
BaseGenericInternalRow也有3个子类,分别是GenericInternalRow、SpecificInternalRow和 MutableUnsafeRow类。
其中MutableUnsafeRow和UnsafeRow相关,用来支持对特定的列数据进行修改。
GenericInternalRow类源码
//构造参数是Array[Any]类型,采用对象数组进行底层存储、
// 注意:数组是非拷贝的,因此一但创建,就不允许通过set操作进行改变。
1 class GenericInternalRow(val values: Array[Any]) extends BaseGenericInternalRow { 2 /** No-arg constructor for serialization. */ 3 protected def this() = this(null) 4 5 6 def this(size: Int) = this(new Array[Any](size)) 7 8 // 也是直接根据下表访问的 9 override protected def genericGet(ordinal: Int) = values(ordinal) 10 11 12 override def toSeq(fieldTypes: Seq[DataType]): Seq[Any] = values.clone() 13 14 15 override def numFields: Int = values.length 16 17 18 override def setNullAt(i: Int): Unit = { values(i) = null} 19 20 21 override def update(i: Int, value: Any): Unit = { values(i) = value } 22 }
而SpecificInternalRow则是以Array[MutableValue]为构造参数的,允许通过set操作进行修改。
final class SpecificInternalRow(val values: Array[MutableValue]) extends BaseGenericInternalRow {
TreeNode体系
无论是逻辑计划还是物理计划,都离不开中间数据结构,在Catalyst中,对应的是TreeNode体系,TreeNode类是Sparksql中所有树结构的基类,TreeNode内部包含一个Seq[BaseType]类型的变量children来表示节点,TreeNode定义了foreach、map、collect等针对节点操作方法,以及transformUp和transformDown等遍历节点并对匹配节点进行相应转换。
TreeNode一直在内存里维护,不会dump到磁盘以文件形式存储,且无论在映射逻辑执行计划阶段还是优化逻辑执行计划阶段,树的修改都是以替换已有节点的方式进行。
TreeNode体系
TreeNode基本操作
除上述操作外,Catalyst中还提供了节点位置功能,即能够根据TreeNode定位到对应的SQL字符串中的行数和起始位置,该功能在SQL解析发生异常时能够方便用户迅速找到出错的地方
1 // 在TreeNode类中 2 3 case class Origin( 4 line: Option[Int] = None, // 行号 5 startPosition: Option[Int] = None) // 偏移量 6 7 object CurrentOrigin { 8 private val value = new ThreadLocal[Origin]() { 9 override def initialValue: Origin = Origin() 10 } 11 12 13 def get: Origin = value.get() 14 def set(o: Origin): Unit = value.set(o) 15 16 17 def reset(): Unit = value.set(Origin()) 18 19 20 def setPosition(line: Int, start: Int): Unit = { 21 value.set( 22 value.get.copy(line = Some(line), startPosition = Some(start))) 23 } 24 25 26 def withOrigin[A](o: Origin)(f: => A): A = { 27 set(o) 28 val ret = try f finally { reset() } 29 ret 30 } 31 }
作者:于二黑
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。