一、背景介绍
随着智能城市建设的不断升温,海量的时空数据也基于现代的智能设备和卫星定位系统不断产生。在这个过程当中,因为传统的技术无法解决海量时空数据的管理问题,所以出现了很多新技术和新方法,Geomesa就是针对时空场景的开源数据引擎的优秀代表。
Geomesa在时空数据方面,一方面完全兼容了OGC规范,可以对时空数据进行非常方便的操作,另一方面它能够基于分布式大数据组件来对时空数据进行存储和查询,这样可以实现存储节点的弹性扩容,更好地管理海量的时空数据。
但是Geomesa也存在一些问题,比如Geomesa的核心组件利用ECQL作为交互查询语言,不是标准SQL,因此很多嵌套查询的操作很难执行,只能执行一些比较简单的基础查询,用户上手门槛也较高。为了解决上述的问题,Geomesa在其扩展模块中,尝试使用Geomesa Spark SQL来实现复杂SQL,虽然一定程度上能够解决上述的问题,但是也引发了很多新的问题。首先,由于使用Spark作为执行引擎,只能通过Spark Submit来进行任务的提交和执行,非常耗时,也不稳定。其次,Spark作为一款典型的离线批处理的OLAP组件,数据处理以批量处理为主,用户往往只能等待所有数据处理完成以后才能拿到结果,非常不友好。最后,Spark SQL本身的优化器Catalyst对很多查询算子无法进行下推,导致数据需要先从存储中抽取出来,然后在Spark中运算执行,底层存储引擎的数据执行能力被完全屏蔽了,导致很多简单查询也需要很长时间。
为了解决上述的问题,JUST基于Calcite和Geomesa,自研了一套面向时空数据场景的数据库优化器。
二、架构介绍
为了解决上述提到的问题,我们自研了一套SQL查询优化器,作为用户与Geomesa交互的中间层,负责请求的接收、查询的组织以及数据的回传,如图所示。左图为SQL优化器的整体架构,右侧是SQL优化器的内部结构。
在与用户进行交互的位置,我们支持了Java SDK、CMD命令行客户端和Python SDK三种交互方式,下面分别是服务层、解析层、校验层、优化层以及执行层。而且在执行层内部,我们封装了JUST现有的时空数据模型(ST Data Model)以及时空分析算法,使用户能够真正通过调用SQL,就可以完成海量时空数据的建模和分析操作。
三、分层设计与实现
3.1 服务层
在服务层,我们也曾经尝试过用原生的Geomesa Spark SQL来进行服务的提供,甚至还使用Spark Job Server来对Spark Context进行管理。但是由于这条技术路线最终的任务支撑还是Spark,Spark Context本身不稳定问题仍然无法根本解决,服务会经常挂掉,而且由于Spark SQL本身是批量数据处理并回传的逻辑,具体来说,就是需要将所有数据处理完成才会将数据进行回传,所以服务的执行延时也非常明显,寻找对用户更加友好的服务提供方式的需求就非常迫切。
基于上述原因,我们在自研查询优化器的服务层,从两个方面来解决上述的问题。一方面,我们基于Avatica实现了一套遵循JDBC规范的服务接口,这样用户就可以直接调用JDBC相关的接口对时空数据库底层进行调用。另一方面,我们也实现了流式的数据回传方式,用户在执行数据查询操作时,数据会以迭代器的形式进行回传,即数据的流式回传,更加友好地服务用户。
3.2 解析层
在解析层,原生的Calcite是利用JavaCC作为解析器,但是其学习成本是比较高的,语法文件中混杂了一些代码逻辑,不易阅读。为此,我们使用Antlr 4来对语法解析层进行了重构,Antlr 4作为一个优秀的语法解析组件,语法文件易读,而且自动生成的词法和语法解析器都非常便于操作,其访问者模式的接口扩展性也非常好。使用Anrlr 4使我们在进行语法规范的规约时非常便捷,后期进行语法扩展的难度也大大降低。
3.3 校验层
在优化器当中,校验层的作用是将语法树规约为执行算子。在这个过程当中,会对查询语句中涉及到的库表元数据、字段数据类型以及函数逻辑进行合法性检验。
原生的Calcite对空间数据类型的支持比较薄弱,因此我们在实现过程当中,在其中也实现了对时空数据类型的支持。例如,Calcite虽然可以通过Spatial扩展,来支持Point、Linestring、Polygon等基本空间数据类型,但是对于Trajectory、RoadNetwork这种复杂的时空数据类型支持得就不是很好。通过我们的扩展,在校验层可以对这些类型进行一些识别,进而扩充了优化器处理数据的能力。
3.4 优化层
优化层的任务是对关系代数的算子进行组织和等效转换,因此优化层能否做出最优的查询执行计划就非常重要。我们做了如下优化:
首先,原生的Geomesa由于只支持ECQL这种比较简单的语法规范,因此查询优化非常简单,尤其是对子查询以及嵌套语句支持非常弱。我们在优化器当中,使用Calcite来对复杂的关系代数算子进行管理,比如说各种子查询操作,面向时空数据场景的Spatial Join操作等等,都能够很好地支持。
第二,针对Spark SQL优化器无法对很多特定算子(尤其是聚合函数)进行下推的问题,我们也基于Calcite进行了优化,尤其是聚合查询,我们可以直接将其交给存储引擎来执行,充分调动底层数据存储引擎自有的数据处理能力。
3.5 执行层
在执行层,Geomesa和Calcite都只实现了一些比较通用性的功能,为了增强我们优化器的能力,我们对这两个开源组件作了较多的改进,主要分为以下几个方面:
首先,对于时空数据模型,Geomesa并没有对其进行逻辑封装和业务抽象,因此我们将JUST自封装的时空数据模型整合其中,可以更加方便地针对不同的业务场景,管理对应的时空数据。
第二,我们也整合了JUST自有的可插拔的算法模块,支持了大量的时空分析操作。例如我们现在支持对轨迹数据进行降噪、分段、驻留点分析以及地图匹配等全流程的预处理功能。
第三,我们在底层执行中,利用了动态代码生成技术,解决了传统数据库查询优化器中普遍存在的CPU频繁切换上下文的问题。
第四,除了对开源组件的封装,我们对Geomesa等开源组件也进行了增强,解决了其原有BUG,实现了很多QQ号买号平台地图新的算子,最终可以在特定场景下表现出来非常好的性能。
四、性能对比
针对Geomesa原生API方式、Geomesa Spark SQL 搭载Spark Job Server的方式以及我们研发的SQL优化器,我们进行了对比试验。
机器配置如下:
我们基于数据量大小这个维度,设置了三个测试梯度:350万条数据、1000万条数据、5000万条数据,示例数据如下:
在每一种场景当中,我们执行了相同的查询逻辑:
语句1:select count(*) from test_5
语句2:select max(attr1) from test_5
语句3:select min(attr1) from test_5
语句4:select avg(attr1) from test_5
语句5:select sum(attr1) from test_5
语句6:SELECT * from test_5 where st_within(order_position,st_makebbox(116, 39, 116.5, 39.5))
语句7:select * from test_5 where st_contains( st_geomfromwkt(‘POLYGON((00, 0 100, 100 110, 110 0, 0 0))’), order_position )
语句8:SELECT * from test_5 where st_crosses(order_position,st_makebbox(116, 39, 116.5, 39.5))
语句9:SELECT * from test_5 where st_intersects(order_position,st_makebbox(116, 39, 116.5, 39.5))
语句10:SELECT * from test_5 where st_overlaps(order_position,st_makebbox(116, 39, 116.5, 39.5))
语句11:select * from test_5 where st_disjoint( st_geomfromwkt(‘POLYGON((9090, 111 111, 111 80, 80 90, 90 90))’), order_position )
语句12:select * from test_5 where attr1 >900 and attr1 <990
语句13:select * from test_5 where order_time>‘2020-12-21’ and order_time <‘2020-12-31’
最终的执行结果如下:
在数据量为350万条数据的情况下,不同查询语句的结果如图所示
在数据量为1000万条数据的情况下,不同查询语句的结果如图所示
在数据量为5000万条数据的情况下,不同查询语句的结果如图所示。
综合来看,SQL优化器对时空数据的查询全面超过了Geomesa Spark SQL的技术方案,相对于原生Geomesa,在空间查询和聚合函数方面也有非常明显的提升。