SparkSQL 体系结构
SparkSQL体系结构如下图所示,整体由上到下分为三层:编程模型层、执行任务优化层以及任务执行引擎层,其中SparkSQL编程模型可以分为SQL和DataFrame两种;执行计划优化又称为Catalyst,该模块负责将SQL语句解析成AST(逻辑执行计划),并对原始逻辑执行计划进行优化,优化规则分为基于规则的优化策略和基于代价的优化策略两种,最终输出优化后的物理执行计划;任务执行引擎就是Spark内核,负责根据物理执行计划生成DAG,在任务调度系统的管理下分解为任务集并分发到集群节点上加载数据运行,Tungsten基于对内存和CPU的性能优化,使得Spark能够更好地利用当前硬件条件提升性能
SparkSQL 编程模型 - DataFrame
说到计算模型,批处理计算从最初提出一直到现在,一共经历了两次大的变革,第一次变革是从MR编程模式到RDD编程模型,第二次则是从RDD编程模式进化到DataFrame模式。
第一次变革:MR编程模型 -> DAG编程模型
和MR计算模型相比,DAG计算模型有很多改进:
可以支持更多的算子,比如filter算子、sum算子等,不再像MR只支持map和reduce两种
更加灵活的存储机制,RDD可以支持本地硬盘存储、缓存存储以及混合存储三种模式,用户可以进行选择。而MR目前只支持HDFS存储一种模式。很显然,HDFS存储需要将中间数据存储三份,而RDD则不需要,这是DAG编程模型效率高的一个重要原因之一。
DAG模型带来了更细粒度的任务并发,不再像MR那样每次起个任务就要起个JVM进程,重死了;另外,DAG模型带来了另一个利好是很好的容错性,一个任务即使中间断掉了,也不需要从头再来一次。
延迟计算机制一方面可以使得同一个stage内的操作可以合并到一起落在一块数据上,而不再是所有数据先执行a操作、再扫描一遍执行b操作,太浪费时间。另一方面给执行路径优化留下了可能性,随便你怎么优化…
所有这些改进使得DAG编程模型相比MR编程模型,性能可以有10~100倍的提升!然而,DAG计算模型就很完美吗?要知道,用户手写的RDD程序基本或多或少都会有些问题,性能也肯定不会是最优的。如果没有一个高手指点或者优化,性能依然有很大的优化潜力。这就是促成了第二次变革,从DAG编程模型进化到DataFrame编程模型。
第二次变革:DAG编程模型 -> DataFrame编程模型
相比RDD,DataFrame增加了scheme概念,从这个角度看,DataFrame有点类似于关系型数据库中表的概念。可以根据下图对比RDD与DataFrame数据结构的差别:
直观上看,DataFrame相比RDD多了一个表头,这个小小的变化带来了很多优化的空间:
RDD中每一行纪录都是一个整体,因此你不知道内部数据组织形式,这就使得你对数据项的操作能力很弱。表现出来就是支持很少的而且是比较粗粒度的算子,比如map、filter算子等。而DataFrame将一行切分了多个列,每个列都有一定的数据格式,这与数据库表模式就很相似了,数据粒度相比更细,因此就能支持更多更细粒度的算子,比如select算子、groupby算子、where算子等。更重要的,后者的表达能力要远远强于前者,比如同一个功能用RDD和DataFrame实现:
DataFrame的Schema的存在,数据项的转换也都将是类型安全的,这对于较为复杂的数据计算程序的调试是十分有利的,很多数据类型不匹配的问题都可以在编译阶段就被检查出来,而对于不合法的数据文件,DataFrame也具备一定分辨能力。
DataFrame schema的存在,开辟了另一种数据存储形式:列式数据存储。列式存储是相对于传统的行式存储而言的,简单来讲,就是将同一列的所有数据物理上存储在一起。对于列式存储和行式存储可以参考下图:
列式存储有两个重要的作用,首先,同一种类型的数据存储在一起可以很好的提升数据压缩效率,因为越“相似”的数据,越容易压缩。数据压缩可以减少存储空间需求,还可以减少数据传输过程中的带宽需求,这对于类似于Spark之类的大内存计算引擎来讲,会带来极大的益处;另外,列式存储还可以有效减少查询过程中的实际IO,大数据领域很多OLAP查询业务通常只会检索部分列值,而不是粗暴的select * ,这样列式存储可以有效执行’列值裁剪’,将不需要查找的列直接跳过。
DAG编程模式都是用户自己写RDD scala程序,自己写嘛,必然或多或少会有性能提升的空间!而DataFrame编程模式集成了一个优化神奇-Catalyst,这玩意类似于MySQL的SQL优化器,负责将用户写的DataFrame程序进行优化得到最优的执行计划(下文会讲),比如最常见的谓词下推优化。很显然,优化后的执行计划相比于手写的执行计划性能当然会来的好一些。下图是官方给出来的测试对比数据(测试过程是在10billion数据规模下进行过滤聚合):
Spark用DataFrame取代RDD以提高性能???
在许多人眼中, RDD是老掉牙的, 而用了DataFrame的Spark 2.1会更快. 然而, 很多人没有意识到Dataframe是基于RDD实现的. 我们可以试着打开引擎盖,看看里面到底是怎么工作的.
在Spark中, DataFrame是一个以命名列方式组织的分布式数据集,等同于关系型数据库中的一个表,也相当于R/Python中的data frames(但是进行了更多的优化). RDD是一个分布式的数据集,数据分散在分布式集群的各台机器上.
下图标示了两者结构上的对比.
左侧的RDD[Person]虽然以Person为类型参数, 但Spark框架本身不了解Person类的内部结构. 而右侧的DataFrame却提供了详细的结构信息, 使得Spark SQL可以清楚地知道该数据集中包含哪些列, 每列的名称和类型各是什么. DataFrame多了数据的结构信息(Schema).
DataFrame运行效率优于RDD,因为它规定了具体的结构对数据加以约束. 由于DataFrame具有定义好的结构, Spark可以在作业运行时应用许多性能增强的方法. 如果你能够使用RDD完美地编写程序,也可以通过RDD实现相同的性能. DataFrame说白了是基于RDD的抽象. 如果你在设计算法和编写代码时花功夫, 你也可以使用RDD得到类似DataFrame甚至更好的速度。
Phases of query planning in Spark SQL. Rounded rectangles represent Catalyst trees.
DataFrame的底层结构是RDD. Spark在你使用DataFrame时会优化你的代码. 如果你编写一个足够高效的代码,你当然可以实现与RDD相同的性能. 然而达到跟DataFrame相同的效率需要更多的努力. 这就是为什么DataFrame更好. 你甚至可以使用Spark SQL. DataFrame以每行表示一条记录的行存储数据. 因此, 用户可以在它们之上运行Spark SQL查询.
Interfaces to Spark SQL, and interaction with Spark.
Spark将整个查询封装成一个单独的代码块(他们称之为整个阶段代码生成),由于避免了虚拟调用(Virtual Call),它导致更好的缓存局部性(Cache Locality).
DataFrame的优势
Spark对于DataFrame在执行时间和内存使用上相对于RDD有极大的优化。
Catalyst优化引擎使执行时间减少75%.
Project Tungsten Off-heap内存管理使内存使用量减少75%, 无垃圾回收器.
Performance of an aggregation written using the native Spark Python and Scala APIs versus the DataFrame API.
Performance of a two-stage pipeline written as a separate Spark SQL query and Spark job (above) and an integrated DataFrame job (below).
我们可以看到,使用python及scala执行RDD的速度明显比DataFrame慢. 在执行RDD时,scala比Python快。但对于DataFrame,两种语言没有区别. 这是因为python是解释型语言,scala是编译型语言. 为了更好地执行,Catalyst将Scala和Python的DataFrame操作编译为物理计划,并生成了JVM(Java Virtual Machine) bytecode,所以python没有解释过程. 导致这两种语言在性能上基本有着同样的表现. 同时, 两者性能均优于普通Python RDD实现的4倍,也达到了Scala RDD实现的两倍.经过Catalyst优化后的代码比解释型代码明显快了很多.
Spark不变的目标就是提供一个单一的平台,让用户可以从中获得更好的分布式算法来匹配任何类型的数据处理任务。其中,性能一直是主要的目标之一,而Tungsten的目标就是让Spark应用程序达到硬件性能的极限。
DataFrame在时间和空间上对于RDD的优势明显,总的来说,要优先选择DataFrame.
结论
Spark SQL是Apache Spark中的一个新模块,提供了关系处理(Relational Processing)的丰富集成。Spark SQL使用声明性DataFrame API扩展Spark以允许关系加工(relational Processing),提供诸如自动优化(Automatic Optimization)等优点, 让用户编写混合了关系和复杂分析的管道(Pipeline)。
它支持大规模定制的广泛功能数据分析,包括半结构化数据(Semi-structured Data),查询联合(Query Federation),和机器学习(Machine Learning)的数据类型。要启用这些功能,Spark SQL基于一个名为Catalyst的可扩展优化器, 它很容易添加优化规则,数据源和数据类型嵌入到Scala编程语言中。
用户反馈和基准测试(Benchmark)显示,Spark SQL使它明显更简单并且更有效地写入混合关系和的数据流水线程序处理,同时相比以前的SQL-on-Spark引擎提供大幅加速.
小结
个人觉得,RDD和DataFrame的关系类似于汇编语言和Java语言的关系,同一个功能,如果你用汇编实现的话,一方面会写的很长,另一方面写的代码可能还不是最优的,可谓是又臭又长。而Java语言有很多高级语意,可以很方便的实现相关功能,另一方面经过JVM优化后会更加高效。