Baf9cf1a27f71c5efd81ee1c7719e6d0
Apache Calcite 为什么能这么流行

上面这张图上列的,是直接使用 Apache Calcite 或者至少相关联的项目。大家肯定能在里面找到很多自己熟悉的项目。

那 Apache Calcite 究竟是干嘛的,又为什么能这么流行呢?

首先,摆一个应该没多少人会反对的共识:SQL 是编程领域最流行的语言。

  • 有 MySQL、Oracle 之类使用 SQL 作为交互语言的数据库
  • 然后有 JDBC、ODBC 之类和各种数据库交互的标准接口
  • 有大量数据科学家和数据分析师等不太会编程语言但又要使用数据的人
  • 第一代大数据计算引擎 MapReduce 被 Hive SQL 很大程度上替代
  • 新一代大数据计算引擎 Spark 很快就推出了 Spark SQL
  • 最近几年大热的流处理引擎 Flink 很快也推出了 Flink SQL
  • ......

这样的例子还可以举出很多。SQL 我们用起来很顺手,但实现起来呢,却并不容易。

比如要给 MongoDB 套上一个 SQL 的壳子,或者要想直接用 SQL 查一堆 CSV 文件,恐怕没多少人能顺利的自己实现。

Apache Calcite 的出现,让你能够很容易的给你的系统套上一个 SQL 的壳子,并且能提供足够高效的查询性能优化。

下面不会想很多帖子一样,去解释 Calcite 是怎么做到这一点的,这样的东西太多了,没有必要重复。

今天,我想从设计的角度聊下为什么 Calcite 能这么流行。

足够简单和 focus 的定位


通常我们可以把一个数据库管理系统分为上图的五个组件。Calcite 在设计之初就确定了自己只关注和实现图中绿色标识的三个部分,而把灰色部分的数据管理和数据存储留给了各个外部存储/计算引擎。

通常数据管理和数据存储,尤其是后者是很复杂的,也会由于数据本身的特性导致实现上的多样性。Calcite 抛弃了这两部分,而是专注于上层更加通用的模块,使得自己能够轻装上阵,系统的复杂性得到控制,开发人员的精力也不至于铺的太开。

另一方面,Calcite 没有去重复造各种轮子,在该用现成的东西的时候,就直接用能找到的最合适的。这是个非常好的习惯,但也是非常容易被程序员抵触的方法。

比如,作为一个 SQL 解决方案,关键的 SQL 解析这一步,Calcite 没有选择造轮子,而是直接使用了开源的 JavaCC,来将 SQL 语句转化为 Java 代码,然后转化成一颗 AST 供下一阶段使用。

另一个例子,为了支持后面会提到的灵活的元数据功能,Calcite 需要支持运行时编译 Java 代码。默认的 JavaC 太重,需要一个更轻量级的编译器,Calcite 同样没有选择造轮子,而是使用了开源的 Janino 方案。

足够简单和 focus 的定位,不重复造轮子,使得 Calcite 的实现足够简单和稳定。

灵活可插拔的架构

上面的图是 Calcite 官方给出的架构图。

一方面印证了我们上面提到的,Calcite 足够简单,没有做自己不该做的事;另一方面,也是更重要的,Calcite 被设计的足够模块化和可插拔。

JDBC Driver 这个模块用来支持使用 JDBC client 的应用;SQL Parser and Validator 模块用来做 SQL 解析和校验;Expressions Builder 用来支持自己做 SQL 解析和校验的框架对接;Operator Expressions 这个模块用来处理关系表达式;Metadata Providers 用来支持外部自定义元数据;Pluggable Rules 用来定义优化规则;最核心的 Query Optimizer 则专注查询优化。

功能模块的划分足够合理,足够独立,使得不用完整集成,而是可以只选择其中的一部分使用;而基本上每个模块都支持自定义,也使得用户能更多地定制系统。

上面的图展示了 10 多种框架对 Calcite 的集成情况。可以看到像 Hive 就自己做了 SQL 解析,只使用了 Calcite 的查询优化功能;而 Flink 则从解析到优化都直接使用了 Calcite。

Flink 对 Calcite 的使用,从这个 Flink 的架构图看的会更清楚。Flink 提供了 Table API 和 SQL API 两种形式来支持对格式化数据的处理。 SQL API 通过 Calite 的 SQL Parser 和 Validator 转成逻辑执行计划,而 Table API 直接转换成 Calcite 的逻辑执行计划。二者在这里达到统一,再通过 Calcite 做优化,完了之后再转成对应的物理执行计划,Table API 对应对 DataSet 的操作,SQL API 对应对 DataStream 的操作。

以上说的集成方法,都是把 Calcite 的模块当做库使用。如果觉得太重量级,可以选择更简单的 Adapter 的方式。通过类似 Spark/Flume 这些框架里自定义 Source/Sink 的方式,来实现和外部系统的数据交互操作。

上图列的就是集中典型的 Adapter,比如通过 MongoDB 的 Adapter 就能直接在应用层通过 SQL,而底层自动转换成 Java 和 MongoDB 完成交互。当然如果社区没有现成的实现,也很容易自己按照 API 规范去实现一个。

对多种异构数据源的支持

除了对标准 SQL 的支持,Calcite 还支持各种丰富的数据源。

上图所示,就是对 MongoDB 的支持,通过 _MAP[key] 的方式就能很方便的访问到 mongo 里面的半结构化数据。

上图就是对流数据的支持,通过 STREAM 关键字,扩展了 Calcite 的能力,当然还支持窗口函数等比较高级的流功能。

top Created with Sketch.