接手被MySQL卡死的数据,TDengine在能源管理系统的应用

小 T 导读:杭州基础创新科技有限公司自主研发的IEMS能源管理系统是一个对建筑水能、电能等各分类分项能耗数据进行采集、处理,并分析建筑能耗状况,实现建筑节能应用等功能的物联网大数据平台。之前该平台用MySQL存储抄表数据,数据量达到1500万条时就出现较长的查询时延甚至卡死,遂采用TDengine Database,经对比,性能比MySQL提升近50倍。

背景

能源管理系统中数据量最大的就是各个智能抄表、传感器上报的数据。整个杭州市从2017年7月开始推出天然气智能抄表,到2018年就有37万用户接入,使用人次超过了77万。此外还有智能水表、电表等源源不断的上报数据。

这些智能抄表的上报数据要进行长期记录,并定期查询、统计生成报表;在系统中,也需要在用户点击查看具体设备时,显示出该设备的实时读数以及历史变化曲线等。

随着公司业务的不断扩大,所有客户的数据我们还需要一个总的平台来监测存储,传统的关系型数据库已经不足以支撑业务量,因此针对抄表数据时序结构化的特点选择合适的数据库变得非常重要。本文我们就来介绍一下杭州基础创新IEMS系统中,对当前最流行的时序数据分析引擎TDengine的使用。

系统功能架构IEMS能源管理系统由主站、NB蓝牙采集终端(无线通讯不需要)、计量设备等组成;系统主站又由通信服务、业务应用、数据库、接口、后台服务所组成;数据库服务由数据存储、计算服务、数据展现组成。用户开通计量收费功能后,通过网络式预付费电能表和此系统软件,可以实现预付费管理、用电数据在线采集抄表、费用统计、预付费充值售电业务管理等基本功能,实现欠费断电,充值后送能的、便于管理的远程能源管理模式。整个系统架构比较庞大,包含如下图所示的分层结构。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 1 1

公司在开发IEMS能源管理系统的开发迭代中开始使用TDengine时序数据库,主要用于工商业水电能源管理监测系统的各种智能抄表设备上报数据的存储和管理,搭建一个能够承载百万级别抄表数的实时和历史数据存储(如下图所示):智能抄表采集数据通过集中器发给系统,系统将数据实时写入TDengine数据库。再基于TDengine数据平台,进一步做各种业务相关的应用层开发比如设备数据实时查询、历史记录即席查询以及一些周期性报表工作。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 2 1

为什么在IEMS系统中选用TDengine?

在系统前期的版本中,我们使用了MySQL数据库来存储设备上报数据。但接入系统的设备量日益增长、用户对数据实时性反馈的要求也越来越高,MySQL已经无法满足业务需求。我们发现将设备数据转移到时序数据库TDengine Database中存储是个更好的解决方案。

查询效率提升

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抄表类型。

                   Field        |      Type      |  Length   |  Note  |
========================================================================
sjsj                            |TIMESTAMP       |          8|        |
jssj                            |TIMESTAMP       |          8|        |
yxzt                            |BINARY          |         10|        |
sjzt                            |BINARY          |        128|        |
jdqzt                           |BINARY          |         10|        |
axdy                            |FLOAT           |          4|        |
bxdy                            |FLOAT           |          4|        |
cxdy                            |FLOAT           |          4|        |
axdl                            |FLOAT           |          4|        |
bxdl                            |FLOAT           |          4|        |
cxdl                            |FLOAT           |          4|        |
sydl                            |FLOAT           |          4|        |
zyggl                           |FLOAT           |          4|        |
axyggl                          |FLOAT           |          4|        |
bxyggl                          |FLOAT           |          4|        |
cxyggl                          |FLOAT           |          4|        |
zwggl                           |FLOAT           |          4|        |
axwggl                          |FLOAT           |          4|        |
bxwggl                          |FLOAT           |          4|        |
cxwggl                          |FLOAT           |          4|        |
zglys                           |FLOAT           |          4|        |
axglys                          |FLOAT           |          4|        |
bxglys                          |FLOAT           |          4|        |
cxglys                          |FLOAT           |          4|        |
zxygzdl                         |FLOAT           |          4|        |
zxygzdl1                        |FLOAT           |          4|        |
zxygzdl2                        |FLOAT           |          4|        |
zxygzdl3                        |FLOAT           |          4|        |
zxygzdl4                        |FLOAT           |          4|        |
axwd                            |FLOAT           |          4|        |
bxwd                            |FLOAT           |          4|        |
cxwd                            |FLOAT           |          4|        |
lxwd                            |FLOAT           |          4|        |
hjwd                            |FLOAT           |          4|        |
meter_no                        |BINARY          |         12|tag     |
project_id                      |BINARY          |         20|tag     |
gateway_no                      |BINARY          |         12|tag     |
meter_type                      |BINARY          |         32|tag     |

查询设备最新状态时,我们就可以从设备ID、所属项目、或者设备类型等不同的维度作为查询条件,直接从TDengine的超级表查询所有满足筛选条件的所有设备。例如查询所有设备的最新数据记录,并按照meter_no, gateway_no, project_id, meter_type进行分组显示,只用一条SQL语句即可,语句如下:

select last_row(*) from db.sb_fhsj 
group by meter_no,gateway_no,project_id,meter_type;

这里也可以对标签进行值过滤,比如根据业务需要筛选出某个项目的某类设备的最新数据:

select last_row(*) from db.sb_fhsj
where project_id=$condition1 and meter_type=$condition2
group by meter_no,gateway_no,project_id,meter_type;

对单一某个设备过去一天的数据进行查询,直接对meter_no进行筛选:

select * from db.sb_fhsj
where meter_no=$condition1 and _c0 >= now -1d

这些查询非常直白,只要表结构设计合理,业务代码很容易开发。

– 命令数据

命令数据是由系统下发指令返回的操作结果。TDengine的单表查询速度远快于MySQL,因此我们还用它保存一些不需要复处理的日志数据。


       Field                    |      Type      |  Length   |  Note  |
=======================================================================
sjsj                            |TIMESTAMP       |          8|        |
cmd                             |BINARY          |          2|        |       
frame                           |BINARY          |       1000|        |
upordown                        |TINYINT         |          1|        |
body                            |BINARY          |       2048|        |
project_id                      |BINARY          |         20|tag     |
gateway_no                      |BINARY          |         12|tag     |

– 原始报文

原始报文是设备下发上行的未解析数据。查询时是按照时间范围检索,因此我们也直接将其存储在TDengine中。采用了几个较长的binary字符串字段。注意对于历史遗漏数据补足,插入时须使用IMPORT代替INSERT完成插入。

 Field                          |      Type      |  Length   |  Note  |
=======================================================================
sjsj                            |TIMESTAMP       |          8|        |
task_id                         |BINARY          |         64|        |
cmd                             |BINARY          |          4|        |
message                         |BINARY          |       1024|        |
data                            |BINARY          |       2048|        |
meter_no                        |BINARY          |         12|tag     |
project_id                      |BINARY          |         20|tag     |
gateway_no                      |BINARY          |         12|tag     |

相关系统UI界面介绍

工厂项目设备导入 – 将物联网设备接入平台,设备采集数据就会实时写入TDengine。在此页面上会对每个设备的实时值进行定期的刷新,即维护一个定时任务来执行last_row()查询。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 3

运维平台总览 – 抄表结算及近期告警总览。告警信息存储在告警表,后台会轮询告警表看是否有告警状态的信息,然后及时显示在设备检测页面。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 4

运维项目总览 – 抄表实时状态统计显示,通过对抄表上报的运行状态进行实时查询和统计。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 5

负荷数据分析 – 设备各个负荷指标历史数据查询分析。这就涉及到用户的选择,需要看那张表的历史数据,直接在前端页面选择时间范围和设备ID,通过TDengine秒级反馈历史数据记录并绘制变化曲线。

TDengine 时序数据库 - 接手被MySQL卡死的数据,TDengine在能源管理系统的应用 6

总结

在使用过程中,作为一款为物联网场景设计的时序数据库,TDengine确实展现了在设备多、采集频率高的情形下显示出其性能高、架构简答的优势。相同的设备实时数据查询场景,TDengine比MySQL快了近50倍;其超级表的设计省去了不少联表查询逻辑,大大简化了业务层的开发工作。我们当前的系统已经发挥出其数据缓存和时序索引的能力,会在后面继续探索使用下TDengine的流式计算和订阅的功能,充分发挥底层数据库的功能,再进一步优化平台的系统架构。

作者简介:

张鉴,基础创新UI设计师,2017年加入杭州基础创新科技有限公司研发部,工作至今,目前负责公司系统软件的整体视觉设计,包括WEB端与移动端的产品界面设计工作,2019年-2020年期间主导了IEMS能源管理系统的UI开发设计。

公司简介:

杭州基础创新科技有限公司自主研发的IEMS能源管理系统是一个对建筑的水能、电能等各分类分项能耗数据进行采集、处理,并分析建筑能耗状况,实现建筑节能应用等功能的物联网大数据平台。系统实现了对水、电等的抄表自动化管理,包括对抄表数据的自动采集、查询、统计、结算、远程控制、大数据分析、能耗设备监控,并输出各类形式的图文、曲线、报表等,最终达到让企业能源管理更智能、更节能、更安全的目的。通过计量仪表能耗数据集中采集,能源计量设备管理等多种手段,也能够让用户体验分类分项查询、统计、结算、充值,退款,报表等功能,使管理者对企业的能源成本比重,发展趋势有准确的掌握。