源码分析系列推荐:
【Flink】第四篇:【迷思】对update语义拆解D-、I 后造成update原子性丢失
【Flink】第十五篇:Redis Connector 数据保序思考
【Flink】第十六篇:源码角度分析 sink 端的数据一致性
【Flink】第二十四篇:源码角度分析 DataStream API 调用逻辑
【Flink】第二十五篇:源码角度分析作业提交逻辑
【Flink】第二十六篇:源码角度分析Task执行过程
【Flink】第二十七篇:三天撸了一个 Flink SQL 字段血缘算法
接上篇 【Flink】第二十七篇:三天撸了一个 Flink SQL 字段血缘算法 ,从本篇开始深入Flink SQL的解析流程及原理。
本文内容:
- Apache Calcite介绍
- 从源码工程中一瞥Flink SQL中的Calcite
DSL & GPL
通用编程语言(General Purpose Language):
可以用来编写任意计算机程序,并且能表达任何的可被计算的逻辑,同时也是图灵完备的。例如,Java、C、Python。
领域专用语言(Domain Specific Language):
能够高效的描述特定领域的世界观和方法论的语言。例如,SQL、HTML & CSS、Regex。
平衡本质:
DSL 通过在表达能力上做妥协以换取在某一领域内的高效表达 (世界级软件开发大师 Martin Fowler 对于DSL的解释)。有限的表达能力就成为了 GPL 和 DSL 之间的一条界限。DSL高效简洁的领域语言,与通用语言相比能极大降级理解和使用难度,同时极大提高开发效率的语言。
DSL需要有特定解析器对其进行构建:
- 没有计算和执行的概念;
- 本身不需直接表示计算;
- 只需声明规则和事实及某些元素之间的层级和关系;
解析器概念
功能:
1. 设计词法、语法、语义:定义 DSL 中的元素是什么样的,元素代表什么意思
2. 实现 Parser,对 DSL 解析,最终通过解释器来执行
核心概念:
1. 抽象语法树(Abstract Syntax Tree,AST):
抽象语法树是源代码结构的一种抽象表示,它以树的形状表示语言的语法结构。
抽象语法树一般可以用来进行代码语法的检查,代码风格的检查,代码的格式化,代码的高亮,代码的错误提示以及代码的自动补全等等。
2. 词法解析器 Lexer:
词法分析是指在计算机科学中,将字符序列转换为单词(Token)的过程。
3. 语法解析器 Parser:
语法解析器通常作为 编译器 或 解释器 出现。它的作用是进行语法检查,并构建由输入单词(Token)组成的数据结构(AST)。
常见解释器:Apache Antlr、SQLParser、Apache Calcite(JavaCC)
Apache Antlr
概念:
它的鼻祖级工具是lex、yacc。 举例,如何将java源码转换成字节码?实现这个需求,需要按照java规范,将源码中的每个词法(如public、class、package)、类名、包名等转换成对应的字节码。那么如何取得这些词、类名、包名、变量名呢? 正则表达式在这里可能就显得力不从心了。因为除了要寻找这些词法外,还需要处理复杂的上下文关系(如变量的作用范围)。这些正是antlr擅长的地方。
谁在使用:Hive、Spark、Oracle、Presto、Elasticsearch
核心组件: 词法Lexer 语法Parser
1. 词法Lexer:
- 标识符,即各类编程语言中所说的以下划线、字母开头的字符串
- 字面量,英文叫Literal,其实就是可以当作值的东西,放在操作符两边。如数字、单引号字符串、双引号字符串、各个进制写法等
- 字符,单字符(!、~、=、>等)、双字符(>=、<=)等
- 关键字,如Java中的class、package、import、public等
2. 语法Parser:
- 例如,变量定义、类定义
词法和语法规则配置放在 .g4 文件里。
Apache Calcite
概念:
是面向 Hadoop 新的查询引擎,它提供了标准的 SQL 语言、多种查询优化和连接各种数据源的能力,除此之外,Calcite 还提供了 OLAP 和 流处理 的查询引擎。
使用Calcite作为SQL解析与处理引擎有:Hive、Drill、Flink、Phoenix、Storm。
历史:
起源于Hive,原名optiq,为 Hive 提供基于成本模型的优化。2014年成为Apache孵化项目,并更名Calcite。建设者是Julian Hyde,曾经是 Oracle 引擎的主要开发者、SQLStream 公司的创始人和主架构师、Pentaho BI 套件中 OLAP 部分的架构师和主要开发者。现在他在 Hortonworks 公司负责 Calcite 项目。
设计目标:
“ one size fits all (一种查询引擎,连接多种前端和后端)”,希望能为不同计算平台和数据源提供统一的查询引擎,并以类似传统数据库的访问方式(SQL 和高级查询优化)来访问Hadoop 上的数据。
设计目标是成为动态的数据管理系统,所以在具有很多特性的同时,也舍弃了比如数据存储、处理数据的算法和元数据仓库。在应用和数据存储及数据处理引擎之间很好地扮演中介的角色。
特性:
1. 支持标准 SQL 语言;
2. 独立于编程语言和数据源,可以支持不同的前端和后端;
3. 支持关系代数、可定制的逻辑规划规则和基于成本模型优化的查询引擎;
4. 支持物化视图(materialized view)的管理(创建、丢弃、持久化和自动识别);
Calcite 的物化视图是从传统的关系型数据库系统(Oracle/DB2/Teradata/SQL server)借鉴而来,传统概念上,一个物化视图包含一个 SQL 查询和这个查询所生成的数据表。
物化视图可以进一步扩展为 DIMMQ(Discardable, In-Memory, Materialized Query)。简单地说,DIMMQ 就是内存中可丢弃的物化视图,它是高级别的缓存。
5. 基于物化视图的 Lattice 和 Tile 机制,以应用于 OLAP 分析;
6. 支持对流数据的查询。
Calcite 对其 SQL 和关系代数进行了扩展以支持流查询。Calcite 的 SQL 语言是标准 SQL 的扩展,而不是类 SQL,这个差别非常重要。
核心组件:
1. 模板引擎FreeMarker
语法模板文件 parserImpls.ftl 配置文件 conf.fmpp -> .jj 模板文件
2. 语法解析器JavaCC
.jj 模板文件 -> 生成解析器代码文件 .java
在Flink源码工程中的体现:
工程机理:
例如,Flink SQL中的 WATERMARK FOR AS 定义水位线,我们来看看涉及到哪些东西:
1. parserImpls.ftl:
主要完成:
(1) 定义三个成员变量:eventTimeColumnName、pos、watermarkStrategy
(2) 为事件时间属性名eventTimeColumnName赋值
(3) 为字符串位置偏移量pos赋值
(4) 为水位线所属的AST树节点SqlNode赋值watermarkStrategy
这里的赋值是由calcite codegen生成的解析器代码完成的(下节介绍),而SqlWatermark是引入的类,我们看一看这个SqlNode:
这个SqlWatermark本质是对SqlNode的规则定义,继承自SqlCall,UML如下,
所以本质就是一个SqlNode。
但是在哪里引入的SqlWatermark类呢?我们看Parser.tdd
而在这个文件的开始有这个定义:
FlinkSqlParserImpl即为Calcite根据DSL文件描述文件parserImpls.ftl生成的类名的定义。
而parserImpls.ftl和Parser.tdd是如何产生联系的呢?我们看config.fmpp,
至此,我们大致了解Flink是如何在工程角度与Calcite相遇的,更多细节限于笔者能力和时间有限就不过多展开了。下一篇将介绍Calcite在Flink中的解析流程及一些细节。