知识卡片
DataFrame的设计价值:用一套共享的执行流程,统一原本各自为战的多语言多查询引擎
内容
在引入DataFrame API之前,Spark只能通过SQL(包括Hive SQL)的方式查询结构化数据,而不同的查询引擎各自有独立的优化器和执行器、采用不同的中间数据结构——这带来了两个明显的痛点:一是很难把不同引擎各自积累的优化成果合并到一起(一个引擎里做的优化没办法自动惠及另一个引擎);二是想要新支持一种查询语言非常困难(每支持一种新语言,几乎要重新搭建一套完整的解析、优化、执行链路)。DataFrame API的解法是对结构化数据的表示方法做了一次高度抽象:DataFrame本质上是带有Schema(结构信息)的RDD,用户在DataFrame上应用一系列表达式操作,最终都会生成同一种树形的逻辑查询计划,这个逻辑计划统一经历语义分析(Analysis)、逻辑查询优化(Logical Optimization)、物理查询优化(Physical Planning)、代码生成(Code Generation)这几个阶段,最终产出可执行的RDD——不管上层用的是Spark SQL、Hive SQL、DataFrame原生API还是R语言,除了最开始”解析这门语言写的语句”这一步各自不同之外,后续所有阶段全部共享同一套执行流程。这个设计带来的直接好处是:任何一项在这套共享执行流程里做出的优化(比如Tungsten项目对内存和CPU使用效率的改进),都能同时惠及所有上层语言和接口,而不需要在每种语言各自的实现里重复优化一遍;同时因为解析层和后续执行流程是解耦的,未来要支持一种新的查询语言,只需要新增一个”把这门语言翻译成统一逻辑查询计划”的解析器,不需要重新搭建整套优化和执行体系。这个案例给出了一条系统架构设计的重要思路:当一个系统需要同时支持多种”表层接口”(这里是多种查询语言),却又希望底层的优化成果能够被所有接口共享时,关键是要在表层接口和底层执行引擎之间,设计出一层足够抽象、能够承载所有接口语义的中间表示(这里是DataFrame的逻辑查询计划)——把”翻译成中间表示”和”基于中间表示做优化执行”这两件事彻底解耦,才能让多个表层接口真正共享同一套底层能力,而不是各自维护一套互不相通的优化和执行逻辑。
结构图:
flowchart TB
A["Spark SQL"] --> E["统一逻辑查询计划(DataFrame)"]
B["Hive SQL"] --> E
C["DataFrame原生API"] --> E
D["R语言API"] --> E
E --> F["Analysis 语义分析"]
F --> G["Logical Optimization 逻辑优化"]
G --> H["Physical Planning 物理优化"]
H --> I["Code Generation 代码生成"]
I --> J["可执行的RDD"]
K["Tungsten项目等底层优化"] -.->|"惠及所有上层语言"| F