点位即设备上用于采集、监测和控制的各类传感器和执行器,是物联网应用场景中最重要的数据单位,通过对众多点位进行管理,企业可以实现设备监控预警、实时监测等方案。高效地存储和管理点位数据对于每一个企业和用户而言都是至关重要的。在本篇文章中,我们将详细介绍如何使用 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"}
这种场景下,如果采用宽表模式进行建模,会导致数据稀疏的问题,设备下的点位数越多,数据会越稀疏。
time | carID | elec | speed | engineStatus |
---|---|---|---|---|
2024.01.01 00:00:00.000 | 0001 | 80.17 | ||
2024.01.01 00:00:01.000 | 0001 | 80.29 | ||
2024.01.01 00:00:10.000 | 0001 | 90 | ||
2024.01.01 00:00:20.000 | 0001 | 89 | ||
2024.01.01 00:02:00.000 | 0001 | level1 | ||
2024.01.01 00:04:00.000 | 0001 | level2 |
针对这种场景,业界有两种典型的窄表建模方案。方案一为每一种不同数据类型的点位维护一张数据表。以上述数据为例,一共有 INT、DOUBLE、STRING 三种类型的点位,需要建立三张数据表进行管理存储。如下所示:
- 电池电量表(INT 类型点位)
time | carID | elec |
---|---|---|
2024.01.01 00:00:10.000 | 0001 | 90 |
2024.01.01 00:00:20.000 | 0001 | 90 |
- 车速表(DOUBLE 类型点位)
time | carID | speed |
---|---|---|
2024.01.01 00:00:00.000 | 0001 | 80.17 |
2024.01.01 00:00:01.000 | 0001 | 80.29 |
- 发动机状态表(STRING 类型点位)
time | carID | engineStatus |
---|---|---|
2024.01.01 00:02:00.000 | 0001 | level1 |
2024.01.01 00:04:00.000 | 0001 | level2 |
此方案解决了数据稀疏的问题,通过维护不同的数据表来存储类型不同、采样频率不同的点位数据。但是也相应地引入了新的问题,即此方案需要维护的表数量会随着点位类型的增加而变多,给管理、维护和使用带来了不便,比如查询不同的点位时,需要访问不同的表对象。
针对此痛点,业界的优化方案是将所有的点位数据合并成一张表。具体做法是,将所有点位类型转换成字符串进行存储,这样仅需维护一张表便能管理所有的点位数据。以上述数据为例:
time | carID | TagName | value |
---|---|---|---|
2024.01.01 00:00:00.000 | 0001 | speed | 80.17 |
2024.01.01 00:00:01.000 | 0001 | speed | 80.29 |
2024.01.01 00:00:10.000 | 0001 | elec | 90 |
2024.01.01 00:00:20.000 | 0001 | elec | 90 |
2024.01.01 00:02:00.000 | 0001 | engineStatus | level1 |
2024.01.01 00:04:00.000 | 0001 | engineStatus | level2 |
合并表之后,用户执行写入和查询操作时,只需访问一个表对象,大大提高了易用性。但是,此方案将数据转换成字符串,不仅增加了存储成本,还降低了查询和计算的效率。
结合上述两种方案的优缺点,DolphinDB 推出了 IOTDB 引擎,以解决点位数据在存储管理、查询计算效率上的痛点。
2. DolphinDB IOTDB 引擎介绍
DolphinDB IOTDB 引擎支持可变类型 IOTANY,能够存储不同类型的数据。通过 IOTANY 类型,可以实现一张表存储、管理所有类型的点位数据。IOTANY 类型写入时,会将不同类型的数据分组,将同一种类型集中存储在一起,相比于前述两种方案,管理、存储、查询和计算都更加高效。
同时,IOTDB 引擎内部维护了一张点位最新值缓存表,对所有点位数据最新值进行缓存,可以大幅提升点位最新状态的查询性能。
如上图所示,DolphinDB IOTDB 引擎由三部分构成:TSDB 引擎、点位最新值缓存表、点位静态信息表。TSDB 引擎负责存储点位的历史数据,IOTANY 列将在 TSDB Level File 层级单独存储不同类型的值;点位最新值缓存表负责缓存所有点位在时间戳上的最新数据,每一点位只会缓存一条;点位静态信息表主要记录点位数据的真实类型,初次写入时,系统会将数据类型记录到静态信息表中,后续每一次写入都会检查待写入点位的类型与静态表的类型是否一致,类型不一致会抛出异常。
3. 点位管理引擎典型存储建模案例
首先设想一个数据采集场景:自来水管道有压力、流速、浊氯等级、阀门开关、状态五个类型的传感器。这五个类型的传感器采集的数据类型如下表所示:
指标 | 数据类型 | 示例 |
---|---|---|
压力 | FLOAT | 12.37 |
流速 | DOUBLE | 113.676769 |
浊氯等级 | INT | 5 |
阀门开关 | BOOL | true |
状态 | STRING | GBXF_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 引擎 | 性能比 |
1 | 0.976 ms | 37.983 ms | 38 |
100 | 8.217 ms | 5,558 ms | 600 |
1w | 31.286 ms | 479,488 ms | 15,000 |
15w | 147.866 ms | —— | —— |
4. 小结
在本文中,我们首先详细介绍了物联网场景下点位数据建模和管理的难点,以及 DolphinDB IOTDB 引擎的独特优势。文章展示了一个入门级的 IOTDB 引擎应用实例,通过案例演示了由一张点位表存储、管理诸多数据类型不同的点位数据。
在接下来的 IOTDB 引擎系列教程中,我们将深入探讨一个更为复杂的应用场景。这个场景不仅包括异构点位数据的存储与写入、实时状态查询,还将涵盖数据采集、实时监控以及智能分析点位数据的高级技巧,方便大家全面掌握 IOTDB 引擎的强大功能,并将其应用于更广泛的物联网解决方案中。