Why make wheels ?
“宇宙的尽头是SQL!”,相信从 MapReduce 时代过渡过来的开发,在第一次接触分布式 SQL 引擎都会惊叹出这句话。低代码化的潮流,让 SQL 语言快速蔓延到更多的基础设施上面。但不得不说,SQL 也存在它的短板,首先它最早为了关系型数据库设计的,适合查询而非 ETL,但是现在人们慢慢把他扩展到 ETL,批流处理,甚至 AI 上,它就有点吃力了。 第二个问题是,他是声明式的,导致缺乏可编程性。
“一只通晓万物的白泽诞生”,Byzer 以 DATA+AI 为核心理念,语法中融合了类 SQL 语法和宏命令,开创性的制定了一套核心 API*,*简单易用又不失灵活性。
-
从数据处理的角度看,它具备丰富、多样性的数据源扩展和 ET 扩展;
-
从特征工程和机器学习的角度看,语言原生支持模型的训练、部署、预测全流程(train/register/predict),几行代码完成一个模型开发。
通过Byzer,开发人员可以只关注业务逻辑,学习成本低,容易理解,而且内置了很多扩展插件,还有功能强大的 Web UI,可以简化开发复杂度。
How is it designed ?
我们在前面文章《ET 开发指南》中演示了 ET 的设计和如何自定义一个 ET,并详细的介绍了一些 ET 如何在真实场景中解决我们数据和 ML 的业务问题。在惊叹于 Byzer-lang 如此灵活的同时,作为开发者一定会很好奇其内部的实现原理,接下来让我们一探其究吧!
基本概念
我们先来看一下解析器生成器的一些基本概念。
-
抽象语法树 (Abstract Syntax Tree,AST) 抽象语法树是源代码结构的一种抽象表示,它以树的形状表示语言的语法结构。抽象语法树一般可以用来进行
代码语法的检查
,代码风格的检查
,代码的格式化
,代码的高亮
,代码的错误提示
以及代码的自动补全
等等。 -
语法解析器 (Parser) 语法解析器通常作为
编译器
或解释器
出现。它的作用是进行语法检查,并构建由输入单词(Token
)组成的数据结构(即抽象语法树)。语法解析器通常使用词法分析器(Lexer)
从输入字符流中分离出一个个的单词(Token
),并将单词(Token
)流作为其输入。实际开发中,语法解析器可以手工编写,也可以使用工具自动生成。 -
词法分析器 (Lexer)
词法分析
是指在计算机科学中,将字符序列
转换为单词(Token
)的过程。执行词法分析
的程序便称为词法分析器。词法分析器(Lexer)
一般是用来供语法解析器(Parser)
调用的。
我们再看下上述概念如何在 Byzer-lang 的语法解析器中运用。
在 Byzer-lang 中我们将一套完整的 SQ L词法和语法规则定义在 DSLSQL.G4 文件中,内容是使用标准的 antlr4 语法定义的 DSL,antlr4 会基于我们的语法规则 codegen 成相应的代码。
在 Byzer-lang中 解析一条代码的大概流程如下:
一条代码经过词法解析器 DSLSQLLexer 生成一系列的 TokenStream,传递到语法解析器DSLSQLParser 中生成 AST。DSLSQLParser 部分我们其实没有使用 AST,本质上是 codegen 生成的 。而 Byzer expression(就是if/ese 里面的表达式则完整走了整个流程,里面是有 AST 部分的,并且AST 会被 codegen 成 SQL 引擎能执行的 SQL。
另外,Byzer-lang 中的语法提示复用了Byzer-lang 的 lexer 和 Spark SQL 的 lexer,重写了parser 部分。因为代码提示有一个特点是在书写过程中,大部分情况下语法都是错误的、未书写完成的,无法使用严格的 parser 来进行解析。因此我们结合了 spark 的 schema infer,以及重写了 SQL 的解析器,所以可以做到很好的效果。
由于本篇文章重点是讲解 ET 处理流程,antlr4 就介绍到这里,我们会在后续文章中揭秘我们的语法解析的实现内幕。
技术架构
架构图释义:
-
Byzer-lang 通过控制台( Byzer Notebook 或 Byzer Desktop)来完成任务开发、数据湖管理、系统配置、运行记录等操作。任务开发支持任务执行(query)、语法解析(analyze)、语法提示(suggestion);
-
Byzer-lang 支持多种 API 交互方式,可以和上层 Byzer Notebook 通信,包括的方式有:JDBC、HTTP、LSP 协议(language server protocol)、CLI;
-
Byzer-lang 核心的包含了 Byzer-lexer, Byzer-parser, Byzer-codegen 三个部分。其中 Byzer-lexer/Byzer-parser 使用的解析器生成器为 antlr4,完全兼容 Spark SQL语法;另外
!if
分支语法使用的自研的 Byzer expression,这样可以不局限于 antlr4 框架的 DSL 限制,实现比如谓词下推的一些语法层面优化;codegen 是指 Byzer-lang 中的 adaptor 系列,比如:TrainAdaptor、SelectAdaptor 等,Adaptor 的具体功能,会在原理解析中介绍。 -
开发者们提交过来执行的类 SQL 代码,最终会执行在 Byzer 的运行时引擎上面,根据代码的调用方式,分别提交到 Spark 和 Ray 引擎上面执行,除了这两个,Byzer 还支持 Java 或者 Scala 代码的本地执行。
原理解析
我们还以上面邮件服务为例,其 Byzer 语句为:
run command as SendMessage.``
where method="mail"
and content="${EMAIL_BODY}"
and from = "userAccountNumber@qq.com"
and to = "${EMAIL_TO}"
and subject = "${EMAIL_TITLE}"
and contentType="text/html"
and attachmentContentType="text/csv"
and attachmentPaths="${savePath}"
and smtpHost = "smtp.qq.com"
and smtpPort="587"
and `userName`="userAccountNumber@qq.com"
and password="***"
;
SendMessage 是我们提供的一个内置ET,Byzer-lang在语法设计的时候预留了足够的扩展性,我们可以很容易的使用ET的接口新增新的ET满足我们的定制化需求。如需了解ET的接入方式,请参考文章:
用户在 Byzer Notebook编写完一个代码后,点击执行(run)会提交到运行态引擎Byzer-lang的RestController,根据代码处理类型等配置交给JobManager进行同步或异步管理任务。代码会传递给ScriptSQLExec执行器,在执行器中会调用DSLSQLLexer和DSLSQLParser做语法解析。
由于我们使用的是观察者模式(Antlr4支持观察者模式被动触发语法解析,还支持访问者模式由开发者主动触发语法解析),需要传递我们生成好的ScriptSQLExecListener到Antlr4解析器中。
语法解析后会触发Listener的回调函数exitSql。 在exitSql我们根据AST中Token的关键词确定当前执行的语法,run语法会使用TrainAdaptor执行对应ET SendMessage的train方法,这样就访问到了我们在ET中定义的train逻辑进行发送邮件了。由于Byzer中的ET,实际上就是定义了对表操作的扩展接口(SQLAlg),通过实现接口,我们可以在代码中定义具体的ET处理逻辑,实现表进表出的概念 —— 输入一张表,通过ET加工后输出一张表。在邮件工具中,我们的输出就是邮件内容和邮件的附件。
上面就是如何在 Byzer 中实现 SQL 扩展的全部流程,基于这种扩展方式,我们可以很方便地集成一个通用能力到 Byzer 中,也可以规范化扩展插件的接入,并将这种规范化拓展到增强自动代码提示、ET 管理工具、权限控制等功能上。是不是很有趣呢,快加入我们一起探索吧!