单表高效管理异构数据:点位管理引擎快速上手指南

点位即设备上用于采集、监测和控制的各类传感器和执行器,是物联网应用场景中最重要的数据单位,通过对众多点位进行管理,企业可以实现设备监控预警、实时监测等方案。高效地存储和管理点位数据对于每一个企业和用户而言都是至关重要的。在本篇文章中,我们将详细介绍如何使用 DolphinDB 点位管理引擎( IOTDB 引擎 )来管理和存储复杂的点位数据,提升点位数据管理效率。

本教程提供完整的测试代码和测试数据,初学者也可以跟随教程内容复现业务场景。本教程涉及以下 DolphinDB 概念,读者可以按需进行查询和扩展阅读:

1. 点位数据建模概述

点位数据的存储建模方案与数据类型、数据传输方式有很大关联。以下述场景为例:设备有多个点位,这些点位的数据类型不一样,采样频率也不一样,不同类型的点位数据是单独上传的。以车联网场景为例,采集电池电量、车速、发动机状态等数据。如下所示:

//电池电量
{"time":2024.01.01 00:00:10.000,"carID":"0001","elec":90}
{"time":2024.01.01 00:00:20.000,"carID":"0001","elec":89}
//车速
{"time":2024.01.01 00:00:00.000,"carID":"0001","speed":80.17}
{"time":2024.01.01 00:00:01.000,"carID":"0001","speed":80.29}
//发动机状态
{"time":2024.01.01 00:02:00.000,"carID":"0001","engineStatus":"level1"}
{"time":2024.01.01 00:04:00.000,"carID":"0001","engineStatus":"level2"}

这种场景下,如果采用宽表模式进行建模,会导致数据稀疏的问题,设备下的点位数越多,数据会越稀疏。

timecarIDelecspeedengineStatus
2024.01.01 00:00:00.000000180.17
2024.01.01 00:00:01.000000180.29
2024.01.01 00:00:10.000000190
2024.01.01 00:00:20.000000189
2024.01.01 00:02:00.0000001level1
2024.01.01 00:04:00.0000001level2

针对这种场景,业界有两种典型的窄表建模方案。方案一为每一种不同数据类型的点位维护一张数据表。以上述数据为例,一共有 INT、DOUBLE、STRING 三种类型的点位,需要建立三张数据表进行管理存储。如下所示:

  • 电池电量表(INT 类型点位)
timecarIDelec
2024.01.01 00:00:10.000000190
2024.01.01 00:00:20.000000190
  • 车速表(DOUBLE 类型点位)
timecarIDspeed
2024.01.01 00:00:00.000000180.17
2024.01.01 00:00:01.000000180.29
  • 发动机状态表(STRING 类型点位)
timecarIDengineStatus
2024.01.01 00:02:00.0000001level1
2024.01.01 00:04:00.0000001level2

此方案解决了数据稀疏的问题,通过维护不同的数据表来存储类型不同、采样频率不同的点位数据。但是也相应地引入了新的问题,即此方案需要维护的表数量会随着点位类型的增加而变多,给管理、维护和使用带来了不便,比如查询不同的点位时,需要访问不同的表对象。

针对此痛点,业界的优化方案是将所有的点位数据合并成一张表。具体做法是,将所有点位类型转换成字符串进行存储,这样仅需维护一张表便能管理所有的点位数据。以上述数据为例:

timecarIDTagNamevalue
2024.01.01 00:00:00.0000001speed80.17
2024.01.01 00:00:01.0000001speed80.29
2024.01.01 00:00:10.0000001elec90
2024.01.01 00:00:20.0000001elec90
2024.01.01 00:02:00.0000001engineStatuslevel1
2024.01.01 00:04:00.0000001engineStatuslevel2

合并表之后,用户执行写入和查询操作时,只需访问一个表对象,大大提高了易用性。但是,此方案将数据转换成字符串,不仅增加了存储成本,还降低了查询和计算的效率

结合上述两种方案的优缺点,DolphinDB 推出了 IOTDB 引擎,以解决点位数据在存储管理、查询计算效率上的痛点。

2. DolphinDB IOTDB 引擎介绍

DolphinDB IOTDB 引擎支持可变类型 IOTANY,能够存储不同类型的数据。通过 IOTANY 类型,可以实现一张表存储、管理所有类型的点位数据。IOTANY 类型写入时,会将不同类型的数据分组,将同一种类型集中存储在一起,相比于前述两种方案,管理、存储、查询和计算都更加高效

同时,IOTDB 引擎内部维护了一张点位最新值缓存表,对所有点位数据最新值进行缓存,可以大幅提升点位最新状态的查询性能。

如上图所示,DolphinDB IOTDB 引擎由三部分构成:TSDB 引擎、点位最新值缓存表、点位静态信息表。TSDB 引擎负责存储点位的历史数据,IOTANY 列将在 TSDB Level File 层级单独存储不同类型的值;点位最新值缓存表负责缓存所有点位在时间戳上的最新数据,每一点位只会缓存一条;点位静态信息表主要记录点位数据的真实类型,初次写入时,系统会将数据类型记录到静态信息表中,后续每一次写入都会检查待写入点位的类型与静态表的类型是否一致,类型不一致会抛出异常。

3. 点位管理引擎典型存储建模案例

首先设想一个数据采集场景:自来水管道有压力、流速、浊氯等级、阀门开关、状态五个类型的传感器。这五个类型的传感器采集的数据类型如下表所示:

指标数据类型示例
压力FLOAT12.37
流速DOUBLE113.676769
浊氯等级INT5
阀门开关BOOLtrue
状态STRINGGBXF_001

其中,压力传感器有10000个,流速传感器有10000个,浊氯传感器有10000个,阀门开关传感器有5000个,状态传感器有5000个。

传感器采集的数据上传格式如下所示:

{
    "Time":2024.01.01 00:00:00.000,
    "ID":"press_0001",
    "Value":"12.33",
    "StaticInfo":"SZ"
}

每一个传感器的数据单独上传,上传的数据都包含 Time、ID、Value、StaticInfo 四个字段。其中,Time 为采样时间,ID 为点位编号,Value 为采样值,StaticInfo 为区域信息。

DolphinDB 如何通过一张表来存储管理这些传感器数据呢?

3.1 创建 IOTDB 引擎数据库

首先,创建数据库时,通过指定参数 engine 为 ”IOTDB” 创建点位管理引擎:

create database "dfs://IOTDB" partitioned by HASH([SYMBOL,10]),VALUE([today()]),engine = "IOTDB"; 

创建 IOTDB 引擎数据库时,需要注意以下两点:

  • 第一,分区方案必须为二级分区。
  • 第二,一级分区列必须为点位列,二级分区列为时间列。

3.2. 创建点位数据表

其次,创建数据表时,通过指定参数 latestKeyCache 为 ”true”,开启最新值缓存功能;通过创建含有 IOTANY 类型的列,开启点位管理。

create table "dfs://IOTDB"."data" (
        Time TIMESTAMP [compress = "delta"],
        ID SYMBOL,
        Value IOTANY,
        StaticInfo SYMBOL 
)
partitioned by ID, Time,
sortColumns = ["ID","Time"],
latestKeyCache = true

上述代码中,将 Value 列设置为 IOTANY 类型,所有点位的采样数据都通过 Value 列来存储。

3.3. 写入数据

五种类型传感器点位编号如下所示:

点位类型点位编号示例
压力传感器pressure_000001
流速传感器speed_000001
浊氯等级传感器chloramine_000001
阀门开关传感器valve_000001
状态传感器status_000001

往点位表里写入数据时,不同类型的数据需要分别组装在不同的内存表中,再调用append!将数据写入(JAVA API 写入示例可参照附录)。如下所示:

  • 压力传感器数据(FLOAT)写入:
pt = loadTable("dfs://IOTDB","data")
Time = take(2024.01.01T00:00:00.000,10000)
ID = `pressure_ + lpad(string(1..10000),6,"0")
Value = rand(100.0,10000)$FLOAT
StaticInfo = rand(`SZ`BJ`SH,10000)
t = table(Time,ID,Value,StaticInfo)
pt.append!(t)
  • 流速传感器数据(DOUBLE)写入:
Time = take(2024.01.01T00:00:00.000,20000)
ID = `speed_ + lpad(string(1..20000),6,"0")
Value = rand(100.0,20000)$DOUBLE
StaticInfo = rand(`SZ`BJ`SH,20000)
t = table(Time,ID,Value,StaticInfo)
pt.append!(t)
  • 浊氯等级传感器数据(DOUBLE)写入:
Time = take(2024.01.01T00:00:00.000,10000)
ID = `chloramine_ + lpad(string(1..10000),6,"0")
Value = rand(100,10000)$INT
StaticInfo = rand(`SZ`BJ`SH,10000)
t = table(Time,ID,Value,StaticInfo)
pt.append!(t)
  • 阀门开关传感器数据(BOOL)写入:
Time = take(2024.01.01T00:00:00.000,5000)
ID = `valve_+ lpad(string(1..5000),6,"0")
Value = rand(true false,5000)$BOOL
StaticInfo = rand(`SZ`BJ`SH,5000)
t = table(Time,ID,Value,StaticInfo)
pt.append!(t)
  • 状态传感器数据(STRING)写入:
Time = take(2024.01.01T00:00:00.000,5000)
ID = `status_+ lpad(string(1..5000),6,"0")
Value = rand(["GBXF_001","GBXT_001","GBXF_002","GBXT_002"],5000)$STRING
StaticInfo = rand(`SZ`BJ`SH,5000)
t = table(Time,ID,Value,StaticInfo)
pt.append!(t)

写入的数据如下所示:

select * from pt context by split(ID,"_")[0] limit 3 

由上图可知,不同类型的点位数据存储到了一张表的同一列中。IOTANY 列的引入,给数据建模、数据管理带来了极大的便利性。

3.4. 最新状态查询

在物联网场景中,很常见的一个场景是查询点位的最新状态。我们通过context by+csort+limit -1语法来查询最新值,如下所示:

select * from pt context by ID csort by Time limit -1

针对最新值查询,IOTDB 引擎引入两种优化:

  • 最新值缓存表:引入以分区为单位的最新值缓存表。系统将实时更新最新值缓存,并直接从缓存而非磁盘中读取数据。
  • 存储引擎层的最新值查询优化:在存储引擎层实现 context by 最新值算法,用于支持缓存未命中的情况。

通过上述两种优化,IOTDB 引擎在最新状态查询场景下较 TSDB 引擎有数十倍至数百倍的性能提升,且随着点位数越多,提升越明显。

点位数IOTDB 引擎TSDB 引擎性能比
10.976 ms37.983 ms38
1008.217 ms5,558 ms600
1w31.286 ms479,488 ms15,000
15w147.866 ms————

4. 小结

在本文中,我们首先详细介绍了物联网场景下点位数据建模和管理的难点,以及 DolphinDB IOTDB 引擎的独特优势。文章展示了一个入门级的 IOTDB 引擎应用实例,通过案例演示了由一张点位表存储、管理诸多数据类型不同的点位数据

在接下来的 IOTDB 引擎系列教程中,我们将深入探讨一个更为复杂的应用场景。这个场景不仅包括异构点位数据的存储与写入、实时状态查询,还将涵盖数据采集、实时监控以及智能分析点位数据的高级技巧,方便大家全面掌握 IOTDB 引擎的强大功能,并将其应用于更广泛的物联网解决方案中。

附录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值