目录
概述
Spark SQL组件中DataFrame,DataSets跟RDDs相比,会有比较大的性能优势。
(1)DataFrame和DataSet是一类带Schema的分布式数据结构。其中的Row对象不会造成额外的编译时类型检测成本。
(2)额外的schema信息可以提供更高效的存储层(Tungsten)。
(这里应该指的是DataFrame,DataSets这些存储数据结构,而不是HDFS等这些存储介质)
(3)基于schema信息可以在优化器(Catalyst)中实现更多的优化操作。
Tungsten
Tungsten是Spark的一种数据表达方法,它对Spark执行引擎进行了修改,最大限度地利用现代计算硬件资源,大幅提高了Spark应用程序的内存和CPU效率。基于Tungsten的数据表达法比使用Java和Kryo序列化出来的对象要小得多。Tungsten不依赖于Java对象,并且支持JVM的堆内(on-heap)和堆外(off-heap)内存分配。不仅格式更加紧凑,而且序列化的时间也更快。
UDFs和UDAFs
User-Defined Functions(UDFs)和User-Defined Aggregate Functions(UDAFs)可以用我们使用自定义的代码来扩展Spark DataFrame和SQL相关的API,否则就可能需要暂时将DataFrame转成RDD,完成转换操作之后,再转换回来,而这会造成一定程度的资源消耗。
查询优化器(Query Optimizer)
Catalyst是Spark SQL的查询优化组件,用于针对DataFrame获取查询计划(query plan),并将其转换成一个可执行计划(execution plan)。 大概负责了从三个阶段的逻辑计划的优化工作。
优化的过程可分为两个部分,逻辑计划(logical plan)和物理计划(physical plan)。在logical plan期间,Spark会对代码进行优化,得到经过优化的逻辑计划(Optimized Logical Plan),然后再得到物理计划。
什么是逻辑计划(Logical Plan)?
逻辑计划是指所有需要执行的transformation步骤的抽象。一个逻辑计划就像一颗树,同时表达了schema和data,并由Catalyst负责管理和优化。我们可以使用explain命令来查看一个DataFrame的“计划”。例如:df.filter(“c1!=0”).filter(“c2!=0”).explain(true)。
此时Spark会告诉你没必要使用调用两个filter来执行这条语句,而只需要用一个filter,并结合“and”一起使用就可以了,这样可以提高效率。
逻辑计划的几个阶段
(1)第一阶段,Unresolved/Parsed Logical Plan的生成
Spark在这一步主要验证SparkSQL语法的正确性,然后创建一个空白计划,并没有针对查询中用到的列名,表名进行检查。完成后,这个逻辑计划会被标记为Parsed Logical Plan。
(2)第二阶段,Resolved/Analyzed Logical Plan的生成
第一阶段完成之后,Spark紧接着会获取前面定义的schema来做进一步的检查,例如,当检查到**dataframe.select(“price”)**这条语句时,会去检查“price”的列是否存在,而这在第一阶段不会检查(第一阶段只是检查语法)。顺利完成之后,该计划会被标记为“Analyzed Logical Plan”
(3)第三阶段,Optimized Logical Plan
在这个阶段,“Catalyst”优化器负责优化由第二阶段生成的“Analyzed Logical Plan”。
1)负责检查每一个Stage中那些stage可以并行执行。
2)优化查询操作的执行顺序,以便在多连接的情况下获得更好的性能。
3)通过评估filter子句来优化查询。
在这个阶段会得到“Optimized Logical Plan”。
什么是物理计划(Physical Plan)?
物理优化是Spark内部的优化,在创建“Optimized Logical Plan”之后生成。假设有两个表要做连接查询(join),一个是大表,一个是小表,具有不同数量的分区,分散在集群的不同节点中。Spark会在开始前决定应该优先加入哪些分区,以便更好地优化。
whole-stage code generation
whole-stage code generation(Spark2.X开始,Spark1.X是code generation)是一种运行时动态生成代码机制,是SparkSQL查询优化的最后一步。在SQL语句编译后Operator-Tree中,每个Operator不再执行逻辑,而是通过全流式代码生成技术在运行时动态生成代码,并尽量将所有的操作打包到一个函数中。如果是简单查询,Spark会尽量生成一个Stage,如果是复杂的查询,就可能会生成多个Stage。