龙空技术网

Calcite在大数据平台MongoDB查询中的实践应用

闪念基因 11753

前言:

眼前我们对“菜单树查询sql”可能比较重视,你们都想要了解一些“菜单树查询sql”的相关内容。那么小编同时在网上收集了一些关于“菜单树查询sql””的相关文章,希望大家能喜欢,你们快快来学习一下吧!

1

背景

某业务方在使用 MongoDB 过程中遇到不少线上故障问题,由于公司目前仅支持线上查询 Hive 和 Mysql 数据,因此定位分析只能靠公司基础部门提供的 ELK 日志平台去分析处理,如果恰好没有打印相关逻辑日志,则往往只能重新改造代码加些详细日志重新发布上线再去定位,这无疑增加了业务方整个定位问题的时间周期而且非常麻烦。试想,如果我们能够提供实时查看线上 MongoDB 集合数据的入口再结合 ELK 日志分析,这将使业务方更方便快速定位线上问题。

于是我们调研了各种技术方案,比如 Presto 、 Calcite 等查询引擎,由于 Presto 对 MongoDB 支持不够友好,比如需要单独部署服务端, Schema 动态支持不够友好, MongoDB 集合元数据字段无法准确获取导致需要对线上数据进行特殊处理等问题,而 Calcite 则相对比较轻量级,而且更加成熟,因为 Calcite 在 Hive 、 Flink 、 Drill 和 Storm 等知名大数据开源框架中都有广泛使用 ,有不少范例可供参考改进,我们只需在项目中简单依赖一些Calcite相关Jar且完成较为简单的二次开发即 可集成到项目中使用。因此我们决定采用 Calcite 支持线上 MongoDB 实时查询方案,下面我将从以下几个方面来介绍:

2

Calcite介绍及原理分析

1、简介

Calcite是一款开源的动态数据管理框架,它提供了标准的 SQL 语言、多种查询优化和连接各种数据源的能力,但不包括数据存储、数据处理等功能。

Calcite 的目标是希望能为不同计算平台和数据源提供统一的 SQL解析引擎,但是它只是提供查询引擎,而没有真正的去存储这些数据。

2、核心架构

(图1 Calcite核心架构)

以上为Calcite核心架构设计概览,简要说下核心架构执行流程,首先 Calcite 通过 SQL Parser 和 Validator 将一个 SQL 查询解析得到一个抽象语法树AST,由于 Calcite 不包含存储层,因此它提供了另一种定义 table schema 和 view 的机制—— Catalog 作为元数据的存储空间。之后,Calcite 通过优化器生成对应的关系表达式树,根据特定的规则进行优化。

3、执行阶段

(图2 执行阶段划分)

Calcite 框架的运行主要分为以下四个阶段:

A、Parse:使用 JavaCC 生成的解析器进行词法、语法分析,得到 AST;

B、Validate:结合元数据进行校验;

C、Optimize:将 AST 转化为逻辑执行计划,并根据特定的规则进行优化;

D、Execute:将逻辑执行计划转化成引擎特有的执行逻辑。

另外,对于Cacite技术相关Javacc、关系代数、Cacite模型、规则优化、谓词下推、剪枝等技术知识点不做详细解读,因为不是本文重点,读者可以去找些资料了解即可。

3

如何集成我们知道要使用Calcite集成各种数据源,一般参照以下套路进行:

1、自定义Schema;

2、自定义Table;

3、决定Table的字段类型;

4、使用ScannableTable实现简单的全表扫描;

5、更高级一点的技巧,使用Filterable Table实现谓词下推;

6、更酷一点的技巧,基于TranslateTable使用Rule实现逻辑表达式的转换。

这里要着重说明一下4、5、6这3种不同Table的区别,其中,ScannableTable是最简单的Table实现,采用全表扫描方式实现查询数据,它会直接枚举出表的每一行,网上大部分案例都是用这种来做Calcite入门学习;Filterable Table通过支持Project下推、谓词下推等将过滤条件转换成底层数据源存储的过滤Api,在查询时先通过数据源做过滤筛选从而规避直接全表扫描再由Calcite处理,在一定程度上解决了ScannableTable全表扫描的性能问题;TranslateableTable则是更高级的Table实现,它可以使用计划规则来翻译关系操作符,说直白点就是这种Table对应到Sql,可以支持几乎所有条件查询、排序、分页、分组统计等各种应用场景,基本可以满足生产级需要,而ScannableTable和Filterable Table均不能很好支持分组统计这个核心需求。为满足业务方对线上数据使用多种应用场景需要,因此我们决定采用TranslateTable来支持MongoDB实时数据查询。

接下来,我们将详细介绍如何将Calcite集成到Hades平台中来,具体如下:

1、引入依赖

(图3 Calcite核心Maven依赖)

由于Calcite分支管理比较繁杂,有些低版本是不支持TranslateTable方式,同时由于我们线上MongoDB使用较低版本5.7,导致有些Calcite高版本对MongoDB的客户端驱动不兼容,于是我们筛选了很多分支版本,发现1.19.0这个版本既能支持TranslateTable方式,也能兼容低版本的MongoDB驱动,因此我们决定使用这个版本来支持MongoDB查询。

2、 集成Calcite MongoDB源码并二次开发

(图4 Calcite MongoDB核心源码概览)

我们将Calcite MongoDB子模块代码拷贝至我们项目中,并修改Mode.json文件内容。

(图5 数据库Schema定义文件)

很显然,线上集合(对应Mysql中的表)太多,我们不可能手动一个个去配置这种JSON文件,我们调研发现Calcite支持Schema内联方式加载JSON字符串文件,于是我们决定用数据表来存储这些元数据,每条表记录对应一个MongoDB集合Model.json文件,很好地解决了数据集合过多导致配置SQL繁琐且占用内存过大问题,模型表结构如下所示:

(图6 自定义Calcite Model数据表)

(图7 Calcite Model数据样例)

然后,我们在每次申请权限导Mongo表时会自动触发创建或更新模型元数据,Hades查询时将实时加载表对应的元数据来构建Model Schema模型字符串并传入Calcite内部解析处理,从而查询出结果。

最后,我们发现Calcite这个版本代码虽然支持MongoDB低版本驱动,但是调试发现无法连接公司测试和生产环境MongoDB库,因为公司有安全策略限制,因此我们对MongoDB客户端代码这块进行重构,废弃老代码,换一种新的方式来适配公司特殊的Mongo环境,最后验证完全通过。

(图8 Mongo客户端代码兼容实现方案)

3、菜单树接口改造

由于Hades查询菜单树需要支持三段式(Catalog.Database.Table),我们通过抽象定义MongoDB数据源实例相关信息并使用Mysql数据表进行元数据存储,再结合Mongo客户端SDK接口获取表及字段信息,双管齐下,很好地解决菜单树库表展现问题。

(图9 搜索菜单树)

(图10 菜单树实现逻辑)

4、查询权限逻辑改造

由于MongoDB采用三段式查询,类似于之前Presto查询Mysql方式,因此我们参考Presto如何使用Ranger权限逻辑,重新改造集成了Presto对Mongo的查询和下载权限,然后通过Ares平台导表刷新数据表元数据并由用户发起申请和审批Ranger权限(Mongo查询或下载权限),最后系统发送广播消息通知Hades各节点拉取Ranger权限数据到本地并缓存起来,这样就完成了对MongoDB数据表的权限管控。

(图11 MongoDB权限配置文件)

(图12 Ranger Mongo权限配置)

5、全链路打通

通过以上系列步骤整合改造,MongDB查询或下载涉及的相关环节完全打通并形成闭环,我们给出最终的查询效果呈现如下:

(图13 查询结果展示)

4

踩坑之路

前面我 们分别介绍了集成过程中需要解决的系列主要问题及步骤,很明显集成之路并非一帆风顺,各种大小坑都踩了一遍,下面我们将主要说明下遇到的几个疑难问题以及我们是如何解决的,其中主要有 Jar 冲突、驱动无法识别、 MongoDB 业务字段名存在关键词解析无效、 MongoDB 字段类型识别冲突这几个问题。

1、Jar冲突问题

Calcite相关依赖jar集成到Hades发现有很多jar冲突问题,大部分冲突问题都可以通过排除依赖或jar升降级来处理,但是遇到一个Calcite core用到的第三方开源驱动(org.apache.calcite.avatica)相关的jar冲突(protobuf-java),通过版本升降级或排除都无法解决兼容性问题,于是调研了各种包依赖解决方案,比如Osgi,蚂蚁SOFA Ark,自定义类装载器和maven-shade-plugin方式隔离等,最后采用轻量级包兼容隔离方案maven-shade-plugin插件方式解决jar包冲突问题,通过改写第三方开源驱动依赖包的方式并重新打包开源驱动包到Hades,兼容问题得到解决,这种方案基本上可以解决公司所有jar依赖兼容问题,我认为应该算是目前各种方案中最轻量级的一种做法,其他业务团队以后遇到jar冲突类似场景通过常规方案无法或不好解决的时候也可以参考下这种思路。

(图14 Jar冲突终极解决方案)

2、驱动无法识别问题

集成之前的原生测试案例各种查询都正常,但是集成之后启动Hades(Springboot内嵌Tomcat容器运行)后,运行查询报数据库驱动不存在。

(图15 驱动无法识别异常)

于是对比本地原生测试和容器运行测试的调试视图差异,发现动态创建的驱动实例不一致。

(图16 Tomcat容器运行加载驱动调试图)

(图17 本地原生运行加载驱动调试图)

由此可见多半是两者类加载机制不同导致,因此尝试强制Jvm加载驱动到DriverManager中来(获取连接前加入Class.forName("org.apache.calcite.jdbc.Driver")这行代码),重启应用运行测试发现驱动正常引入,问题完美解决。

(图18 容器运行Calcite驱动正常引入调试图)

3、MongoDB存在字段名使用Calcite关键字问题

由于业务方创建MongoDB集合字段有点奔放,导致线上大量字段命名不够严谨,比如叫date、time、name、path、type、format等,导致Calcite SQL解析时与内部关键字token冲突导致解析失败,因此想了个办法加前缀下划线来转义冲突字段名别名,从而规避字段与关键字冲突问题,经验证解析正常。

(图19 字段名与Calcite关键字冲突兼容实现)

4、MongoDB字段类型识别冲突导致查询失败

由于MongoDB存储的是非结构化数据,字段定义非常灵活,不少查询引擎对其支持并不完善,比如Presto在支持MongoDB时,自行维护一套Schema表结构,但是由于MongoDB的无结构性,在Presto自己生成的这一套Schema中会发现有很多列缺失与错误的情况,通过查看Presto的源码,发现其识别Schema的原理是查找当前表中第一条数据,然后判断其所有列的数据类型,进而当做整个表的Schema定义。

眼尖的同学发现这种方式有太多坑,万一第一行很多数据是空怎么办,根本无法识别全部字段类型,于是有神奇的网友发现了一个不错的解决办法,就是先手动插入一条标杆数据,用于标注正确的MongoDB表结构,让Presto能正确识别,然后再插入剩余的业务数据,这样就能保证Presto的功能正确运行。

但是,对于历史遗留库表集合数据怎么办,我们总不能把线上几百个集合的数据都这样搞一遍,会累死业务方的,显然这种简单粗暴方式不能很好解决现实问题。因此我们决定采用相对合理的折中方案,考虑到我们本质上查询MongoDB是通过建立视图的方式,因此可以将所有字段映射为字符串varchar且长度设置足够长(默认200)的形式由程序自动生成视图sql,只要能完全查询出数据即可,这个能适配所有MongoDB版本特殊类型,经验证查询正常出结果,字段兼容性问题得以平滑实现。

(图20 字段类型识别冲突解决字符串映射视图)

5

总结

以上是我们基于目前公司基础数据平台查询线上各种实时数据现状,提供的一种使用Calcite Sql解决实时查询MongoDB的解决思路与方法,其他的比如实时查询Es、Solr、Kafka等数据源类似需求场景也可以参照此设计思路进行实现。

作者:清枫

来源:微信公众号:八戒技术团队

出处:

标签: #菜单树查询sql