1万传感器,日增3000万条数据——工业物联网数据建模与接入实践指南

0 阅读8分钟

导语: 工业物联网项目里有个常见的滞后效应:字段怎么设计、宽表还是窄表、选哪个存储引擎、分区列怎么定,这些建模阶段的选择往往是项目初期图省事定下来的——设备少、数据量小,怎么定都跑得动,问题看不出来。等到设备越接越多、数据量越滚越大,查询变慢、写入卡顿、想改分区又发现数据已经写了大半年改不动了,当初的选择才开始显出代价,而这时候要付出的往往是数倍的返工成本。

本文以一个实际场景为例,结合 DolphinDB 带大家数据建模阶段的决策逻辑捋一遍,再看看数据该如何顺利接入进来。

以一个典型场景为例

某工厂车间里部署了一万个传感器,每 30 秒上报一次温度、压力、湿度、电压、电流和状态信息。按这个频率算下来,单日数据量接近三千万条

对于这种典型的工业监控场景来说,数据设计的重点不在于“怎么把数据塞进去”,而在于如何把数据组织好,让它既能持续写入,也能高效查询,并且方便后续扩展

第一步:数据建模

建模的第一步不是急着定分区,在这之前需要先想清楚三件事:字段怎么设计,指标应该按宽表存储还是窄表存储,以及查询场景决定了该用哪个存储引擎。

1. 字段设计

字段设计阶段用于确定数据表里需要记录哪些字段、每个字段用什么类型存储——既要覆盖设备上报的全部信息,也要避免类型选得过宽造成存储浪费。

上述场景的字段设计如下:

图片

2. 宽表还是窄表

本场景中温度、压力、湿度等指标由同一设备在同一时间点统一上报,且指标集合相对固定,因此合并成一张宽表,便于按设备、按时间做多指标联合查询,也能减少关联查询开销。如果指标上报频率不一致、字段经常变动,则更适合拆成窄表。

图片

3. 存储引擎

DolphinDB 的两种主要存储引擎里,OLAP 引擎擅长大批量、跨设备的聚合分析(比如全厂设备的月度能耗统计),TSDB 引擎则针对“按设备+时间”的高并发点查和最新值查询做了专门优化——通过排序列和分区裁剪加速这类查询。本例最常见的查询场景正是按单个设备、按时间范围调取数据,因此存储引擎选 TSDB

图片

4. 分区方案

分区是 DolphinDB 组织海量数据的核心方式——查询命中分区列时只扫描相关分区,能大幅减少 I/O;分区太大或太小,都会拖慢查询甚至压垮节点内存。

在 DolphinDB 中,分区方案不是拍脑袋定的,而是取决于两点:

  • 常用查询条件。比如本例最典型的是“按设备+时间”查询,这决定了 device_id 和 ts 是首选的分区列

  • 存储引擎推荐的单分区大小,和分区粒度挂钩,是内部实践给出的一些经验性指标

根据字段设计对数据量进行估算:

  • 单行大小 ≈ 4(device_id) + 8(ts) + 8×5(5个DOUBLE指标) + 4(status) = 56字节

  • 每天总量 ≈ 28,800,000行 × 56字节 ≈ 1.5 GB

TSDB 推荐的单分区粒度为 400MB~1GB,如果只按天分区,每天 1.5GB 会偏大。

所以更合理的方案是:日期 VALUE 分区 + 设备 ID HASH 分区。 这样可以把单分区数据量压下来,也更适合后续查询和写入。另外,HASH 分区数一旦建库后就很难再改,建议结合未来 1~3 年的设备增长预期,一次性规划到 4~5 个

第二步:建库建表

// 步骤1:创建复合分区数据库
create database "dfs://iot_sensor"
partitioned byVALUE(2025.01.01..2025.01.03), HASH([SYMBOL, 5])
engine='TSDB'

// 步骤2:创建分区表
create table "dfs://iot_sensor"."sensor_data" (
    device_id SYMBOL[comment="设备id"],
    ts TIMESTAMP[comment="时间", compress="delta"],
    temperature DOUBLE[comment="温度"],
    pressure DOUBLE[comment="压力"],
    humidity DOUBLE[comment="湿度"],
    voltage DOUBLE[comment="电压"],
currentDOUBLE[comment="电流"],
    status INT
)
partitioned by ts, device_id
sortColumns=["device_id","ts"]

这里有 3 个关键点:

  1. 引擎选 TSDB: 更适合“按设备 + 时间”查询,以及窗口聚合场景。
  2. 分区列同时包含ts和 device_id:和前面的复合分区方案一一对应。
  3. 排序列设为 device_id, ts:排序列用于决定数据写入时的排序和查询索引,且要求最后一列必须是时间列。这样设置按设备查询时,更容易命中排序索引。 TSDB 索引机制示意图 TSDB 索引机制示意图

第三步:数据接入

工业现场的数据入口通常不止一个,写入方式要按场景来选。

(1)API 批量写入

适合网关程序、边缘服务等周期性攒批上传的场景。

DolphinDB 提供了Python、Java、C++、Go 等多语言接口,方便应用侧完成数据写入、查询和计算。

以 Python API 为例:

import dolphindb as ddb

import pandas as pd

# 1. 连接 DolphinDB

s = ddb.session()

s.connect("127.0.0.1", 8848, "admin", "123456")


# 2. 构造一批模拟数据

data = pd.DataFrame({...})


# 3. 上传 DataFrame 到 DolphinDB 会话

s.upload({"data": data})


# 4. 批量写入分布式表

s.run("""

pt = loadTable("dfs://iot_sensor", "sensor_data");

tableInsert(pt, data)

""")

print("write success")

为了适配不同数据规模和实时性要求,DolphinDB API通常提供了批量写入、分区并发写入、流式异步写入等多种写入方式:

在这里插入图片描述

简单来说,可以概括为:

  • 小批量场景tableInserttableAppender 就够用

  • 高并发 / 高吞吐实时场景:建议用 MultithreadedTableWriterPartitionedTableAppender(自动按分区键路由写入)

需要注意的是,受 Python 本身 GIL 锁限制,多线程的 PartitionedTableAppender 写入性能有时反而不如单线程 TableAppender,实际选型建议结合数据量做测试对比。

(2)消息中间件订阅

适合 Kafka、MQTT 等实时链路,能把设备侧和分析平台解耦,同时起到削峰填谷的作用。

DolphinDB 提供了 Kafka、MQTT、RabbitMQ、RocketMQ 等消息队列插件,可以在插件市场(marketplace.dolphindb.cn)查看和下载,也可以通过内置的 listRemotePluginsinstallPlugin 函数远程获取和安装安装,无需单独编译。

下载完成后只需要几行脚本,即可对接消息队列的数据到 DolphinDB。以 Kafka 为例:

loadPlugin("kafka")

consumer = kafka::consumer(...)

kafka::subscribe(...)

kafka::createSubJob(...)

(3)历史数据迁移

适合从 MySQL、Oracle 等关系型数据库迁移。

DolphinDB 提供了一系列插件支持跨数据库间的迁移:

  • 通过 MySQL、MongoDB、Redis、HBase 等专用插件对接

  • 对于没有专用插件、但提供 ODBC 驱动的数据库,通过 ODBC 插件统一对接

  • 通过基于 DataX 的 dolphindbwriter 插件,配合 DataX 生态里现成的 Reader 插件,实现更多数据源的离线同步

本文以 ODBC 插件为例:

loadPlugin("odbc")

conn = odbc::connect("DSN=OracleDB;UID=iot_user;PWD=iot_password")

t = odbc::query(conn, "select device_id, ts, temperature, pressure, humidity, voltage, current, status from sensor_history")

pt = loadTable("dfs://iot_sensor", "sensor_data")

tableInsert(pt, t)

(4)文件批量导入

对于 TXT、CSV 等文本文件,可以通过内置的 loadTextloadTextEx 函数导入;对于特殊文件格式,DolphinDB 也提供了对应插件:

  • Parquet 插件:支持列式存储文件的高效读取和转换

  • Arrow 插件:便于与其他系统进行数据传输并自动完成类型转换

  • HDF5 插件:适合导入科研设备、大型实验装置产生的高频数据文件

另外如果历史数据是压缩包形式,还可以用 zip 插件进行解压。

本文以 loadText 为例:

loadText 用法简单,导入后生成内存表,便于预览数据、检查字段类型和简单清洗,适合小规模数据验证阶段。

// 将小型 CSV 文件加载到内存表

data = loadText("/path/to/small_data.csv")


// 数据清洗

clean_data = select * from data where time between 09:00 and 16:00


// 将内存表追加到分布式表

pt = loadTable("dfs://iot_sensor", "sensor_data")

tableInsert(pt, clean_data)

小结

工业物联网数据建模,核心不是先写代码,而是先把三件事想清楚:数据从哪里来数据量有多大、典型查询是什么。

相比传统关系型数据库,DolphinDB 更适合应对工业场景里高频写入、时序查询和大规模分析的需求;同时它把存储、计算和接入能力整合在一起,也能减少后续系统拼接和二次开发的成本。

在“设备 + 时间”这类场景下,DolphinDB 的 TSDB 引擎配合日期 VALUE 分区、设备 ID HASH 分区和宽表模型,通常就能兼顾写入、查询和扩展。数据接入层面,它也提供了比较完整的方案:小批量场景可以用 API 或文件导入;实时链路可以接 Kafka、MQTT 等消息中间件;历史数据迁移则可以通过数据库插件完成。

把建模和接入这两步先理顺后,后面设备再多、数据再大,系统也更不容易被迫重来。