TDengine在智慧排水系统中的应用介绍

534 阅读8分钟

作者|鼎蓝水务-朱佳伟

小T导读:鼎蓝水务是一家致力于城市水环境综合整治与规划的专业环境服务公司,为国家高新技术认证企业。公司创立于2005年,为国内最早从事城市排水管网运维技术和装备引进、推广及实践的技术型企业。如今鼎蓝水务精耕细作十余年,已发展成为集规划咨询、装备制造、技术研发、系统集成和运营维护全流程个性化菜单式服务的智慧水务企业。

业务场景

智慧排水系统中需要对排水设备中的液位进行监测。通常会在被监测区域部署电流液位传感器,采集电流液位频率。传感器采集的信息通过MQTT协议传到网关,网关收集到信息后会定时推送到业务后台服务,由服务层来做数据存储和实时分析。设备运行数据放在SQLServer里面,液位电流频率等不重要又特别多的数据放在Taos里面。

智慧排水系统整体处理流程

整个智慧排水系统包含数据采集层、数据传输层、数据平台层和业务处理层几个模块,具体的处理流程如下图所示:

在目前的应用场景中,海量的数据来自拦蓄盾检测设备上报的数据。在这些监测数据的处理流程上,数据从网关推送过来后会有一个判断是否实时数据。对于非实时数据,则会流经Redis去重,做报警判断然后写入SqlServer。对于实时数据则直接写入TDengine,不需要再经过Redis。之后前端需要的一些类似液位电流数据等就可以直接从TDengine访问。在这之前,所有数据都是使用SQLServer存储,发现数据量达到2000万后SQLServer的查询时延已经非常慢,不得不做分库分表操作来提高查询速度,但这个解决方法遇到跨库跨表的查询非常不便。

TDengine中的数据建模

TDengine是一个专门为物联网结构化数据流设计的时序数据库,其建库、建表的思路与关系库完全不同,遵循一个数据流隔离的原则。物联网设备产生的数据是按照时间顺序产生的数据流。在智慧排水系统,我们往往关心某一个液位计在一段时间范围内的变化趋势或最大、最小值这些统计量。

用关系型数据库比如SQLServer存储时序数据时,做法通常是所有同类设备的数据都进同一张表,每条记录会包含设备ID、数据采集(或入库)时间戳、采集到的值,并按照时间范围来分表提速。

这种做法的弊端在于查询麻烦且效率低。从关系型数据库中查询某一个设备的数据,就需要通过设备ID把其他设备的数据从大表中过滤掉,且每查询一个设备,就要面临过滤其他设备数据的开销。

TDengine的设计思路是一个数据源(设备)一个表,每个数据源按照时间顺序产生的消息流可以流入一个表中,不与其他数据流混合。表的主键是数据记录采集或入库的时间戳,其他字段是采集的值。

这样在TDengine中查询某个设备的指定时间段数据时,查询就简化为找到该设备的表并按照主键(时间戳)过滤搜索数据记录,效率大大提升。

此外,在TDengine中,我们为了方便管理设备的静态信息,也创建了超级表,并将设备ID和分组等作为标签定义好。

具体到我们的场景中,我们的建库、建表思路如下:

  • 数据库

数据库创建主要依据业务模块来,把泵站、泵闸等上报数据分开存储。

             name              |     created time     |  ntables  |  vgroups  |replica| days  |  keep1,keep2,keep(D)   |  tables   |   rows    | cache(b)  |      ablocks       |tblocks| ctime(s)  | clog | comp |time precision|  status  |==============================================================================================================================================================================================================================================log                             | 19-09-06 10:36:33.888|          3|          1|      1|     10|30,30,30                |         32|       1024|       2048|             2.00000|     32|       3600|     1|     2|us            |ready     |hgm                             | 19-09-08 22:42:58.873|         12|          1|      1|     10|3650,3650,3650          |       1000|       4096|      16384|             4.00000|    100|       3600|     1|     2|ms            |ready     |jlj                             | 19-09-08 23:01:12.251|         17|          1|      1|     10|3650,3650,3650          |       1000|       4096|      16384|             4.00000|    100|       3600|     1|     2|ms            |ready     |bengzhan                        | 19-09-08 23:01:21.895|         93|          1|      1|     10|3650,3650,3650          |       1000|       4096|      16384|             4.00000|    100|       3600|     1|     2|ms            |ready     |bengzha                         | 19-09-08 23:01:29.272|         15|          1|      1|     10|3650,3650,3650          |       1000|       4096|      16384|             4.00000|    100|       3600|     1|     2|ms            |ready     |

  • 超级表

超级表的结构非常简单,采集字段就是时间戳ts和采集值val。但此处定义了两个标签,用于描述具体的点位静态信息。

taos> describe rtdata;                             Field                              |      Type      |  Length   |  Note  |=======================================================================================================ts                                                              |TIMESTAMP       |          8|        |val                                                             |FLOAT           |          4|        |sncode                                                          |BINARY          |         20|tag     |pointcode                                                       |BINARY          |         10|tag     |

taos> show stables;                              name                              |     created time     |columns| tags  |  tables   |====================================================================================================================rtdata                                                          | 19-09-09 18:15:42.243|      2|      2|         12|

  • 普通表

taos> show tables;                           table_name                           |     created time     |columns|                             stable                             |=================================================================================================================================================================sch_8_n_level                                                   | 19-09-09 18:24:22.655|      2|rtdata                                                          |sch_7_n_level                                                   | 19-09-09 18:24:22.620|      2|rtdata                                                          |sch_6_n_level                                                   | 19-09-09 18:24:22.585|      2|rtdata                                                          |sch_29_n_level                                                  | 19-10-30 11:35:01.292|      2|rtdata                                                          |sch_5_n_level                                                   | 19-09-09 18:24:22.550|      2|rtdata                                                          |sch_4_n_level                                                   | 19-09-09 18:24:22.514|      2|rtdata                                                          |sch_30_n_level                                                  | 19-10-29 14:54:53.446|      2|rtdata                                                          |sch_3_n_level                                                   | 19-09-09 18:24:22.479|      2|rtdata                                                          |sch_2_n_level                                                   | 19-09-09 18:24:22.442|      2|rtdata                                                          |sch_10_n_level                                                  | 19-09-09 18:24:25.338|      2|rtdata                                                          |sch_1_n_level                                                   | 19-09-09 18:24:22.408|      2|rtdata                                                          |sch_9_n_level                                                   | 19-09-09 18:24:22.691|      2|rtdata                                                          |

TDengine的优势

高写入速度

用下来感受是TDengine的数据写入性能非常高。现在的接入网关的设备数是23个液位9个频率计9个电流计。总共10个拦蓄盾、3个泵站。3个截留井。 1个泵闸,总共有40张表,每张表每天新增6万条数据,现在每张表的大约三百万条数据。使用TDengine带来的最大好处是不用再考虑SQLServer中的分库分表操作,数据不断写入一个月后,查询时延也没有增加。

降采样查询

TDengine为监控设备数据分析提供了一个非常有用的功能 — interval,即按照时间窗口进行降采样。比如在排水系统中,我们往往要计算拦蓄盾每隔20分钟液位值,这种分析可以简单由下面这个SQL语句实现。

select avg(val) from sch_3_n_level where ts > '2019-10-15 00:00:00' and ts < '2019-11-15 00:00:00' interval(20M);

最新数据显示

在现在的监测系统中,需要大屏实时显示液位计的最新读数。这里正好用上TDengine自带的缓存功能。TDengine为每张表在内存中分配了一定的缓存空间,来存储最热的记录,实时查询效率非常高。查询语法用到了last和last_row。

select last_row(*) from rtdata group by sncode;

进而用VUE和hightopo的构建组态可视化界面,实时显示最新液位数据。网关输出最新液位,后端推送给前端,三维页面用hightopo展示液位。做出来的效果也是非常酷的。

低内存、高压缩比

在使用中,发现TDengine处理13个设备、百万量级的数据量的写入任务,内存开销只有1.5GB。查询时,内存增长觉察不出来。整体内存开销比SQLServer降低了50%。

在数据落盘后,查看/var/lib/taos下的数据文件大小,原来5GB的原始数据,经TDengine压缩后只有80MB,压缩比为1.6%。

未来的展望

整体来讲,TDengine对于处理高频采集的结构化时序数据吞吐量、资源开销和压缩都非常优秀。但目前TDengine还不能像Redis那样做去重以及报警,这块后面也会尝试使用TDengine自带的流式计算和订阅来尝试实现报警,看能否进一步简化架构省掉Redis和SQLServer的工作。希望TDengine官方也能在这方面有更多支持,能越来越棒!

作者简介:朱佳伟,鼎蓝水务高级全栈研发工程师,开源社区爱好者,目前主要负责鼎蓝水务智慧排水系统中水位实时监测系统的开发与维护。

原文首发于:blog.csdn.net/weixin_4399…