能源管理系统中数据量最大的就是各个智能抄表、传感器上报的数据。整个杭州市从2017年7月开始推出天然气智能抄表,到2018年就有37万用户接入,使用人次超过了77万。此外还有智能水表、电表等源源不断的上报数据。
这些智能抄表的上报数据要进行长期记录,并定期查询、统计生成报表;在系统中,也需要在用户点击查看具体设备时,显示出该设备的实时读数以及历史变化曲线等。
随着公司业务的不断扩大,所有客户的数据我们还需要一个总的平台来监测存储,传统的关系型数据库已经不足以支撑业务量,因此针对抄表数据时序结构化的特点选择合适的数据库变得非常重要。本文我们就来介绍一下杭州基础创新IEMS系统中,对当前最流行的时序数据分析引擎TDengine的使用。
系统功能架构
为什么在IEMS系统中选用TDengine?
在系统前期的版本中,我们使用了MySQL数据库来存储设备上报数据。但接入系统的设备量日益增长、用户对数据实时性反馈的要求也越来越高,MySQL已经无法满足业务需求。我们发现将设备数据转移到时序数据库TDengine中存储是个更好的解决方案。
查询效率提升
MySQL中存储抄表数据时,是将公司智能电表、水表等不同设备分开存储的,同类设备的数据存放在一张大表中。现在的抄表数据上报频率普遍在分钟级(5-30分钟不等),第一个试点小区2000只智能电表同时写入压力也不算太大,MySQL还可以承载;但在随着接入表数量的增加、存储时间范围的增长,每类设备中的数据记录数积累过多就会出现查询效率问题。仍以智能电表试点为例,当我们导入了1700只电表30天的数据后,电表总记录数就达到了接近1500万条,此时我们再对某些用户进行月度用电量报表统计时,查询就出现了较大的查询时延甚至卡死,查询某个设备的最新读数速度也大大下降。这还是只一个试点小区的设备数。
因此我们不得不考虑分区、分库和分表来优化,让每类设备写入按照7天的时间范围划分的一系列表中;不同的小区通过分库来处理。这样可以保证每张表中的记录不会太多,查询某天的数据就不会太慢而卡死。
然而分库分表操作给业务层却带来了很大麻烦,经常遇到一些查询需要处理各种跨表、跨库的查询,逻辑复杂易出错。在接触到时序数据库TDengine之后,我们发现它其实已经在其存储层面解决了这个时间维度的数据分区问题。TDengine存储数据文件时是按照时间范围划分数据文件的,默认是10天一个文件,数据文件名就对应着一个时间段。
因此在查询很早之前某天的数据时,可以通过检索时输入的时间筛选范围很快的找到数据所在文件,并对此文件进行搜索。本质上讲,跟之前MySQL的分库分表目的相同,就是缩小被检索文件的大小,提高查询速度。做完分库分表后MySQL中的一张表大概数据量可以做到30多万条,做一次最新纪录的查询,大概花费0.5秒;而TDengine中,存储了2000个电表6个月所有的数据(总计8亿多条),查询一次某个电表的最新数据只需要不到0.01秒,这个性能提升接近50倍!
同时我们尝试了查询某个设备30天的全量数据(1500万条),可以在5秒内完成整个查询,比关系库快了太多。
表结构设计简化
上面提到的分库分表的麻烦已经被TDengine的存储策略解决,在提高查询效率的同时。在使用TDengine时,对于终端设备的分类、分组检索工作也可以通过其超级表标签过滤来实现,进一步简化业务层的逻辑处理。我们平台底层要处理多个不同的项目,每个项目还有自己的设备分组。
在TDengine中,表设计的核心思想是一个终端设备一张表,同类设备的表结构由一个超级表来定义。由于同类型设备的上报数据字段完全一致,超级表下的子表结构也完全一样。
但TDengine中允许对每个设备打标签,标签内容可以是设备的ID、设备所属项目ID、设备的类型编号、设备在项目中的分组编号等静态信息。
对我们的查询场景而言,上面这些信息是经常需要进行过滤和筛选的,放在超级表标签中,可以在查询超级表时在where语句中添加对项目ID等标签的过滤条件来分项目查询设备数据。在关系库中进行同样查询就必然涉及到关联多张表,更复杂且效率也不高。
TDengine中表设计及查询举例
在电表数据采集中,数据主要分为任务数据,告警数据,命令数据,原始报文。TDengine是一个设备一张表的设计思路。因此针对每类设备的上述几种数据,我们都按照设备来建表。
任务数据是电表周期性上送的负荷数据,由数据采集的时间戳和具体电表读数等采集值组成。这个建表思路非常简单,就是先建一张超级表,定义出采集字段,之后对每个设备建立一张表来存储其上报的记录。
告警数据是电表发生异常情况上送的一组,由告警时间和负荷数据组成,字段和任务数据基本一致。我们可以把设备的一些不随时间变化的基本属性定义为标签,例如设备ID、所属项目、类型、上级设备等;随时间变化的各个指标定义为表的各个数据列,例如运行状态、事件告警、继电器状态、ABC三相电压、ABC三相电流、有功功率、无功功率等。一个项目中的告警超级表的结构如下,其中的标签我们定义了:meter_no抄表编号,project_id 项目ID,gateway_no网关ID,meter_type抄表类型。
查询设备最新状态时,我们就可以从设备ID、所属项目、或者设备类型等不同的维度作为查询条件,直接从TDengine的超级表查询所有满足筛选条件的所有设备。例如查询所有设备的最新数据记录,并按照meter_no, gateway_no, project_id, meter_type进行分组显示,只用一条SQL语句即可,语句如下:
这里也可以对标签进行值过滤,比如根据业务需要筛选出某个项目的某类设备的最新数据:
对单一某个设备过去一天的数据进行查询,直接对meter_no进行筛选:
这些查询非常直白,只要表结构设计合理,业务代码很容易开发。
命令数据是由系统下发指令返回的操作结果。TDengine的单表查询速度远快于MySQL,因此我们还用它保存一些不需要复处理的日志数据。
原始报文是设备下发上行的未解析数据。查询时是按照时间范围检索,因此我们也直接将其存储在TDengine中。采用了几个较长的binary字符串字段。注意对于历史遗漏数据补足,插入时须使用IMPORT代替INSERT完成插入。
相关系统UI界面介绍
总结
在使用过程中,作为一款为物联网场景设计的时序数据库,TDengine确实展现了在设备多、采集频率高的情形下显示出其性能高、架构简答的优势。相同的设备实时数据查询场景,TDengine比MySQL快了近50倍;其超级表的设计省去了不少联表查询逻辑,大大简化了业务层的开发工作。我们当前的系统已经发挥出其数据缓存和时序索引的能力,会在后面继续探索使用下TDengine的流式计算和订阅的功能,充分发挥底层数据库的功能,再进一步优化平台的系统架构。
作者介绍
公司简介
点击阅读原文,体验TDengine!
本文分享自微信公众号 - TDengine(taosdata_news)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。