阿里云云原生超融合多模数据库Lindorm广泛支持宽表、时序、对象、文本、队列、空间等多种数据模型,Lindorm Ganos作为Lindorm的时空引擎,将达摩院空天数据库引擎的时空数据库技术与Lindorm深度融合,为Lindorm提供了一站式解决海量轨迹场景的存储和各类查询问题的能力。Lindorm Ganos的典型应用场景之一是车联网轨迹处理,NoSQL“小钢炮”,Lindorm时空引擎Ganos轨迹处理实测 一文介绍了Lindorm Ganos在海量轨迹实时写入、在线轨迹检索、实时围栏报警场景下的能力优势。
业务场景
在某客户的轨迹系统中,需要统计出轨迹点出入某个区域的信息,包括:驶入时间、驶出时间、停留时长、轨迹数量等。比如:
- 统计一辆出租车何时进入了小区,何时驶出小区,过去24小时一共进出了几次等
- 统计一辆卡车何时进入服务区,休息了多久,何时离开,过去24小时内一共有多少量卡车停留休息
- 统计一个道路卡口(如红绿灯口),过去24小时内一共有多少量车驶入驶出,每辆车在卡口的停留时间等
有了这些统计信息后,业务层可以依据这些统计这些信息优化业务系统,比如:
- 某个道路卡口中,每个车辆的停留时间过长,可能是该卡口的服务流程存在问题,需要业务层面考虑优化手段。
- 车队的车辆监控服务中,发现某辆车并未在规定的时间内进入某个小区,也未在指定的服务时间内驶出小区,因而可以辅助判别某个车辆是否按时履约服务。
业务系统每天会收集到大量不同汽车实时上报的车辆信息,将车辆的实时位置、实时属性等信息写入数据库。车辆上传的轨迹点记录可能分布在较大的空间区域内,跨越较长的采集时间范围,数据量可以达到上亿条。统计信息有可能需要实时获取,查询的要求一般是几十秒以内。
对于上述大体量轨迹数据的实时进出统计需求,在不利用时空引擎的情况下,需要通过全表扫描+业务层内存聚合计算实现,这样做的主要劣势在于:
- 数据扫描量过大。只能使用时间列索引,需要对全空间范围的数据进行扫描
- 内存消耗过大。如果数据本来没有按时间排好序,轨迹点需要全部加载到内存进行排序才能重构出轨迹;
- 计算量过大。重构后需要对全空间范围的轨迹点逐个判断是否在给定的精确空间范围内
- 业务系统实现复杂。数据库只能提供聚合能力,轨迹的重构、进出判断都需要在业务层进行
ST_TrajectoryProfile算子
Lindorm Ganos 通过内置ST_TrajectoryProfile来高效的实现出入信息的统计。利用其原生实现的时空索引技术,可有效过滤掉大多数不在查询范围内的数据,减少扫描量、内存使用量,降低计算代价,从而可以有效解决上述的三个问题。具体的解决方法包括:
- 利用空间索引+过滤下推,减少扫描量,减少存储层与查询节点间的数据传输。降低数据库IO,减轻查询节点的内存、计算压力;
- 聚合加速:分组聚合计算并行进行,并将部分聚合逻辑下推到数据所在节点,减少查询节点内存和计算压力,提高聚合效率;
- 进出点计算封装:在聚合算子内部完成进出点的判断和轨迹段进出信息提取,减少业务层操作,使用方便
使用限制:由于在聚合操作前,1、2两步已经过滤掉了不在查询区域范围内的轨迹点,在进行出入点判断时,也就无法知道在查询范围以外的数据信息,因此对轨迹出入的判断有以下几个局限性:
1)划分轨迹段的条件依赖于时间阈值的设定,仅在轨迹点均匀采集点的情况下完全没有误差。对于中断采样情况,需要对返回结果进行额外的业务判断
2)对于出点,只能给出离开区域范围前的最后一个点,需要根据返回数据另外查询离开区域范围后的第一个点
使用示例
上述方案由Lindorm Ganos时空引擎的ST_TrajectoryProfile函数实现,函数具体说明详见ST_TrajectoryProfile
测试表结构
+-------------------+--------------+-------------------------------------+---------+----------------+------------+ | TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME | TYPE | IS_PRIMARY_KEY | SORT_ORDER | +-------------------+--------------+-------------------------------------+---------+----------------+------------+ | default | test | device_code | VARCHAR | true | ASC | | default | test | collect_time | BIGINT | true | ASC | | default | test | create_time | BIGINT | true | ASC | | default | test | latitude | DOUBLE | false | none | | default | test | longitude | DOUBLE | false | none | | default | test | altitude | DOUBLE | false | none | | default | test | gps_type | INT | false | none | | default | test | gps_valid | INT | false | none | | default | test | gps_status | INT | false | none | | default | test | ... | ... | ... | ... | +-------------------+--------------+-------------------------------------+---------+----------------+------------+
表的联合主键为device_code(车辆id)、collect_time(gps位置采集时间)、create_time(记录创建时间),另外还有其它74个非主键属性字段,包括对应collect_time的latitude(纬度)、longitude(经度)等。每辆车的gps按照10s的时间间隔连续采集轨迹点数据。
创建索引
为test表创建z-order二级索引,冗余longitude、latitude两列,避免主表回查
CREATE INDEX zorder_idx ON test (z-order(longitude, latitude)) INLCUDE(longitude, latitude);
进出点查询
要获取test表中的每个设备在指定时间段(2022-08-12 11:12:39 - 2022-08-17 09:39:19)内进出指定区域(以经纬度120.20495, 30.187485810为圆心,半径1000米内的区域)的情况,查询语句如下:
SELECT device_code, ST_TrajectoryProfile(longitude, latitude, collect_time, 10000) FROM test WHERE ST_DwithinSphere(ST_GeomFromText('POINT (120.20495 30.187485810)'), longitude, latitude, 1000.0) and collect_time >= 1660273959000 and collect_time <= 1660700359000 group by device_code;
ST_TrajectoryProfile函数返回各个轨迹段的概要信息(进出点和进出时间)。由于gps采样间隔为10s,ST_TrajectoryProfile的时间阈值参数为10000(ms)。
返回结果
+-------------+-------------------------------------------------------------------------------------------------------------------------------------------+ | device_code | ST_TRAJECTORYPROFILE(longitude, latitude) | +-------------+-------------------------------------------------------------------------------------------------------------------------------------------+ | 13184922496 | {"0":"{\"endY\":30.1830,\"endX\":120.2000,\"startY\":30.1820,\"startTime\":1660273959000,\"startX\":120.1989,\"endTime\":1660283962000}"} | +-------------+-------------------------------------------------------------------------------------------------------------------------------------------+
该示例的返回结果表示在查询时间和区域内,只有一辆车进出了区域一次,进入时间为1660273959000 (2022-08-12 11:12:39),进入点为 (120.1989, 30.1820),离开前最后一次记录的时间为1660283962000 (2022-08-12 13:59:22),离开前最后一个记录点为 (120.2000, 30.1830)
注:该示例collect_time存储格式为LONG类型,可支持的时间字段类型还有TIMESTAMP、TIME
性能表现
测试环境
Lindorm Ganos测试集群:Lindorm 2.4.1版本,三节点16核32GB,1040G性能型云存储
测试数据
测试表结构与示例表结构相同,每条记录为车辆行驶过程中采集上传的一个轨迹点,表大小为3146MB,包含25辆车近3个月在经度范围[115.6253, 122.2817]、纬度范围[24.553466, 33.1788] 内的27045751个行驶轨迹点记录。
查询性能
- 查询的空间范围为半径1000m的圆形区域或面积在1平方千米左右的不规则区域,查询的时间范围均在一天左右。
- 返回时间主要取决于索引过滤后聚合计算的数据量和空间范围过滤条件的复杂程度。测试耗时均在20s以内,部分查询情况如下
查询条件 |
聚合计算数据量(查询范围内记录数) |
查询耗时 |
空间范围:(120.20495 30.187485810) 周边1000m,时间范围:2022-10-20 18:12:48 - 2022-10-21 18:12:48 |
3572683 |
17s |
空间范围:矩形[(120.20322 30.18733),(120.21495 30.188485)],时间范围:2022-10-20 07:06:08 - 2022-10-21 15:26:08 |
23026 |
1s |
空间范围:外包框为[(120.20122 30.186485),(120.21595 30.189483)] 的不规则多边形,时间范围:2022-10-01 08:27:16 - 2022-10-02 13:18:41 |
3008 |
6s |
总结
针对轨迹出入点实时计算这一需求,Lindorm Ganos 结合其原生时空索引技术开发了ST_TrajectoryProfie聚合函数,快速返回给用户在给定时间、空间范围内轨迹进出的时间和位置,能够实现百万级计算量10s级返回,较传统不利用时空引擎的方法有一个量级以上的提升,可以满足实时应用场景的需求。