文章目录
DataFrame与Spark SQL的由来
Spark 已经有了 RDD 这个开发入口,为什么还要整出个 DataFrame 来呢?
RDD 之殇:优化空间受限
高阶函数,它指的是形参为函数的函数,或是返回类型为函数的函数。高阶函数,首先本质上也是函数,特殊的地方在于它的形参和返回类型。
RDD算子(如 map、filter等)都需要一个辅助函数 f 来作为形参,通过调用 map(f)、filter(f) 才能完成计算。Spark 只知道开发者要做 map、filter,但并不知道开发者打算怎么做 map 和 filter。
**在 RDD 的开发框架 下,Spark Core 只知道开发者要“做什么”,而不知道“怎么做”。**这让 Spark Core 两眼一抹黑,除了把函数 f 以闭包的形式打发到 Executors 以外,没有额外的优化空间。
DataFrame 横空出世
针对 RDD 优化空间受限的问题,Spark 社区在 1.3 版本发布了 DataFrame。相比 RDD,DataFrame 到底有何不同呢?我们不妨从两个方面来对比它们的不同:一个是数据 的表示形式(Data Representation),另一个是开发算子。
数据表示形式
DataFrame 与 RDD 一样,都是用来封装分布式数据集的。DataFrame 是携带数据模式(Data Schema)的结构化数据,而 RDD 是不携带Schema 的分布式数据集。恰恰是因为有了 Schema 提供明确的类型信息,Spark 才能有针对性地设计出更紧凑的数据结构,从而大幅度提升数据存储与访问效率。
开发算子
RDD 算子多采用高阶函数,高阶函数的优势在于表达能力强,它允许开发者灵活地设计并实现业务逻辑。而 DataFrame 的表达能力却很弱,它定义了一套 DSL 算子(Domain Specific Language),如select、filter、agg、 groupBy等等,它们都属于 DSL 算子。
尽管 DataFrame 算子在表达能力方面更弱,但是 DataFrame 每一个算子的计算逻辑都是确定的,比如 select 用于提取某些字段,groupBy 用于对数据做分组,等等。因此,Spark 可以基于启发式的规则或策略,甚至是动态的运行时信息,去优化 DataFrame 的计算过程。
总结下来,相比 RDD,DataFrame 通过携带明确类型信息的 Schema、以及计算逻辑明确的转换算子,为 Spark 引擎的内核优化打开了全新的空间。
幕后英雄:Spark SQL
首先,Spark Core 特指 Spark 底层执行引擎(Execution Engine)。而 Spark SQL 则凌驾于 Spark Core 之上,是一层独立的优化引擎(Optimization Engine)。Spark Core 负责执行,而 Spark SQL 负责优化,Spark SQL 优化过后的代码,依然要交付 Spark Core 来做执行。
基于 DataFrame,Spark SQL 是如何进行优化的
Spark SQL 的两个核心组件说起: Catalyst 优化器和 Tungsten
Catalyst 优化器
Catalyst 优化器,它的职责在于创建并优化执行计划,它包含 3 个功能模块:
-
创建语法树并生成执行计划;
-
逻辑阶段优化;
-
物理阶段优化;
Catalyst 优化器是在逻辑优化阶段,基于启发式的规则和策略(如谓词下推、列裁剪), 调整、优化执行计划,为物理优化阶段提升性能奠定基础。
除了逻辑阶段的优化,Catalyst 在物理优化阶段还会进一步优化执行计划。与逻辑阶段主 要依赖先验的启发式经验不同,物理阶段的优化,主要依赖各式各样的统计信息,如数据表尺寸、是否启用数据缓存、Shuffle 中间文件,等等。逻辑优化更多的是一 种“经验主义”,而物理优化则是“用数据说话”。
Tungsten
Tungsten 用于衔接 Catalyst 执行计划与底层的 Spark Core 执行引擎,它主要负责数据结构优化与执行代码优化。
数据结构优化指的是 Unsafe Row 的设计与实现;执行代码优化则指的 是全阶段代码生成(WSCG,Whole Stage Code Generation)。
为什么要有 Unsafe Row
对于 DataFrame 中的每一条数据记录,Spark SQL 默认采用 org.apache.spark.sql.Row 对象来进行封装和存储,使用 Java Object 来存储数据会引入大量额外的存储开销。
为此,Tungsten 设计并实现了一种叫做 Unsafe Row 的二进制数据结构。Unsafe Row 本质上是字节数组,它以极其紧凑的格式来存储 DataFrame 的每一条数据记录,大幅削减存储开销,从而提升数据的存储与访问效率。
WSCG:全阶段代码生成
代码生成,指的是 同一个 Stage,Tungsten 在运行时把算子之间的“链式调用”捏合为一份代码。
完成 Catalyst 和 Tungsten 这两个优化环节之后,Spark SQL把优化过的执行计划、以及生成的执行代码,交付给Spark Core。Spark Core 拿到计划和代码,在运行时利用 Tungsten Unsafe Row 的数据结构, 完成分布式任务计算。