TDengine在数字治理系统中处理轨迹数据的应用

2022-05-24 00:00:00 数据 时序 网格 坐标 巡查

巧遇TDengine


2019年听说TDengine,真正接触是从收到一个直播的链接转发,陶建辉老师在极客时间直播,标题这样描述:35年码龄老程序员给您讲述TDengine的超高性能是如何实现的。直播现场,陶老师大汗淋漓,大家都在说没开空调,主持人解释,陶老师现在高烧,坚持给大家讲解。感性与理性。之后,深圳见面会,老板时间挺忙,赶在后一天去的,没有见到陶老师,见到了我们的侯老师。一聊,相见恨晚。

场景介绍


雅恒做了接近两年的“基层数字网格管理系统”。这个管理系统能够对镇街进行大小网格的划分,以网格内建筑和核心部件为基本信息点,在网格内开展消防、禁毒、环卫、两违等巡查活动,并形成规范上传、下达工作流程规范。但我们这段时间其实也在琢磨这个系统的架构是不是已经老了,该如何优化呢?这时候正好通过TDengine了解到时序数据库的概念,应用后虽然不能一步到位把系统做一个很大的提升,但产品不是应该这样一点点改进吗?

在我们的应用场景中,网格员在使用app和小程序进行巡查活动时,每隔30秒会检查一次移动距离,如果超过20米就会上传一个坐标到服务端。一个街道大约有100名网格员,每月有效巡查距离超过100公里,一年约1200000米;一个街道一年就会产生约6000000条坐标记录(武汉大约有一万七千名网格员)。我们有可能随时翻阅三年前某条巡查线路。按原关系数据库的结构设计,这就需要从坐标记录表里根据巡查线路id检索出一个个的坐标点,然后重组成一条巡查线路。三年的记录总量接近2000万条,此时关系型数据库的查询响应应急非常慢,必须要通过按照时间分库分表来提高性能,但随之而来的问题就是遇到跨库跨表查询时间段处理的麻烦(想想就觉得累)。

我们基于TDengine的特性以及使用原则,把坐标记录数据迁移到这上面来:

  1. 一个网格账号一张坐标记录表,这个网格员巡查产生的坐标记录全都按时间顺序记录到这个表上来;

  2. 巡查线路坐标点的检索按线路的巡查时间范围,时序数据库对时间的处理是天生的优势,这样也降低了跨库的耦合;

  3. 我们还借助了微服务低耦合易扩展的特性,独立出坐标读写模块,以下是完整的系统架构,供参考,欢迎批评指正。



用户直接交互的是应用层,包含:浏览器的web应用、微信小程序或者android平台的APP。其中web应用还与GIS系统进行交互获取地图瓦片和坐标载入显示等信息。支撑应用层的是业务运算后台服务,包含用户管理、权限管理、问题管理等业务运算。底层存储使用了MySQL+ TDengine的配合。业务运算后台还会与其他门户数据中心的第三方服务进行交互,实现整个数字治理系统的各项需求。

底层数据存储上的思考是这样的。业务逻辑相关的数据需要大量账号信息管理、关联查询、业务从属处理,是典型关系数据库应用场景,因此这部分数据存入MySQL;巡查坐标数据是前文提到的海量时序位置信息,涉及更多的是按时间读取数据,按照账号计算轨迹等,是典型的时序结构化数据储存分析场景,非常适合存入TDengine。这里把位置数据从原来的MySQL中摘出来后,通过一个坐标计算微服务来转存、计算巡查坐标数据,实现了原有的业务运算后台与TDengine之间的交互,且不对原来的运算后台产生大量代码改动,非常方便。

数据模型


遵循一台设备一张表的设计思路。在我们系统中是一个用户一张表,用于记录该用户的所有历史轨迹信息。所有子表都基于一个名为“super”的超级表创建,将设备ID定义成一个tag,用于对表进行区分。

快速开发


封装好api,直接用SpringBoot调用JDBC来与TDengine数据源交互即可。下面的示例中,使用SpringBoot+TDengine实时存储GPS坐标,实现过程非常的简单。

 在Linux上开启TDengine服务;

第二步 创建一个的SpringBoot项目,在application.properties中配置TDengine的连接信息(ps:端口默认为0,用户名默认为root,密码默认为taosdata);
server.port=8085
server.servlet.context-path=/api


#taos
taosdata.url=jdbc:TAOS://192.168.1.241:0/db?user=root&password=taosdata
taosdata.driverClassName=com.taosdata.jdbc.TSDBDriver

第三步 在pom.xml中配置相关依赖,下载jar包;
<!-- taos Start -->
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<version>1.0.1</version>
</dependency>
<!-- taos END -->

第四步 建立config文件,对应application.properties中的url和driverClassName;
@Component
@ConfigurationProperties(prefix = “taosdata”)
public class TaosdataConfig {
private String url;
private String driverClassName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
}

第五步 实现相关业务逻辑(ps:与传统关系型数据库一样,采用sql语法)。创建库,建表,插入,查询等操作在Java中实现如下。
建库、建表:
String sql1 = “create database if not exists coor”;
stmt.executeUpdate(sql1);
String sql2 = “use coor”;
stmt.executeUpdate(sql2);
String sql3 = “create table if not exists super (ts timestamp, lng double, lat double) tags (id nchar(32))”;
stmt.executeUpdate(sql3);
String sql4 = “create table if not exists “ + (“u” + userid) + “using super tags(‘” + userid + “’)”;
stmt.executeUpdate(sql4);


插入数据:
String sql5 = insert into “ + (“u” + userid) + “ values(“ + now.getTime()+”,” + lng + “,” + lat + “)”;
stmt.executeUpdate(sql5);


查询:
StringBuilder sql6 = new StringBuilder(“select * from u”)
.append(userid)
.append(“ where ts>=’”)
.append(stime)
.append(“’ and ts <= ‘”)
.append(etime)
.append(“’”);
ResultSet resSet = stmt.executeQuery(sql6.toString());
Timestamp ts = null;
while(resSet.next()) {
ts = resSet.getTimestamp(“ts”);
lng = resSet.getDouble(“lng”);
lat = resSet.getDouble(“lat”);
//业务处理略去…
}

总结


雅恒通过将网格巡查位置数据从关系库MySQL转存入时序库TDengine后,解决了大数据量、长周期查询时的性能和易用性问题,避免了分库分表维护的麻烦。通过微服务的方式把TDengine集成进原有系统,提供时序数据存储和计算服务,整体上对原有系统冲击很小,迁移改造比较顺利。数据压缩率可能也是一个很有价值的考量目标。后续也许会继续查看TDengine自带的流计算等功能,看是否能进一步减轻业务层的计算压力,提高计算资源利用率。

来源 https://www.modb.pro/db/168928

相关文章