.NET 时序数据操作实战:Apache IoTDB连接与 CRUD 完全指南

1. Apache IoTDB

目前工业物联网领域对应的主流开源产品是 Apache IoTDB (Internet of Things Database),它是一个专为物联网时序数据设计的高性能集成数据管理引擎,能够高效地存储、管理和查询大规模时间序列数据。

如果你面对的是其他相似名称(如工业领域的专用版本或某个企业的内部命名),核心的连接与操作思路大体类似。下文将以 Apache IoTDB 为例,提供一套完整的 .NET 连接与 CRUD 解决方案。

2. 环境准备与依赖安装

2.1 基础环境要求

  • .NET SDK:≥ 5.0,或 .NET Framework 4.x
  • Apache IoTDB 服务端:建议 0.13.x 或 0.14.x 版本(需提前部署并启动)
  • Thrift 版本:服务端与客户端使用的 Thrift 版本需匹配(IoTDB 基于 Thrift RPC 框架通信)

2.2 NuGet 包安装

Apache IoTDB 为 C# 用户提供了两种 NuGet 包,可通过 .NET CLI 或 Visual Studio NuGet 管理器安装:

包名 说明 安装命令
Apache.IoTDB C# 原生客户端(推荐) dotnet add package Apache.IoTDB
Apache.IoTDB.Data ADO.NET 标准提供程序 dotnet add package Apache.IoTDB.Data

原生客户端提供更丰富的时序操作接口(如对齐序列、Tablet 批量写入等),ADO.NET 版本则更适合熟悉传统数据库编程模型的开发者。

2.3 其他依赖

  • NLog:建议安装 4.7.9 或更高版本,用于客户端内部日志记录
  • Thrift:≥ 0.14.1(通常是传递依赖,无需手动指定版本)

注意:各组件版本不匹配极易引发难以排查的运行时异常,建议严格按照上述标准版本安装。

3. 创建客户端连接与参数说明

3.1 原生接口建立会话

csharp 复制代码
using Apache.IoTDB;

// 创建会话连接
var sessionPool = SessionPool.Builder()
    .Host("192.168.1.100")   // IoTDB 服务端 IP
    .Port(6667)              // 默认 Thrift RPC 端口
    .User("root")            // 初始超级用户
    .Password("root")        // 初始密码
    .PoolSize(5)             // 会话池大小
    .Build();

// 获取一个可用会话
var session = sessionPool.GetSession();

关键参数说明

方法 参数 默认值 含义
Host(string) IPv4 地址 - IoTDB 所在服务器地址
Port(int) 整数 6667 服务端 RPC 监听端口
User(string) 用户名 "root" 登录用户名
Password(string) 密码 "root" 登录密码
PoolSize(int) 整数 2 连接池并发会话数上限
FetchSize(int) 整数 5000 每次从服务端拉取的行数
TimeOut(int) 整数(毫秒) 60000 单次操作超时时间

3.2 ADO.NET 方式建立连接

csharp 复制代码
using Apache.IoTDB.Data;

var connectionString = "server=192.168.1.100;port=6667;username=root;password=root";
using var connection = new IoTDBConnection(connectionString);
connection.Open();
// 后续使用 IoTDBCommand 执行 SQL 操作

这种方式通过 IoTDBConnectionIoTDBCommand 等标准 ADO.NET 类进行数据库操作,与操作 SqlServer、MySQL 等传统关系型数据库的写法高度一致。

4. 数据模型基础

在具体操作前,先理解 IoTDB 的数据组织方式:

复制代码
root (根节点)
  └── factory (存储组)
       └── workshop (设备)
            └── temperature (物理量/测点) → 时间序列值
            └── humidity (物理量/测点)    → 时间序列值
  • 存储组 (Storage Group):数据隔离与过期策略的基本单位
  • 设备 (Device):对应工厂的一台具体物理设备或一个逻辑采集点
  • 物理量 (Measurement):设备下某个具体的测点(如温度、压力)
  • 时间戳 (Timestamp):数据点的采集时间(长整型,单位毫秒)

一个具体的时序数据点的完整路径为:root.factory.workshop.temperature

5. 核心 CRUD 操作详解

温馨提示:时序场景中"更新"和"删除"通常是低频操作,核心操作集中在"写入"与"查询"。

5.1 创建 (Create) --- 建立存储组与序列

5.1.1 创建存储组
csharp 复制代码
// 设置一个或多个存储组
session.SetStorageGroup("root.factory");
5.1.2 创建时间序列(定义物理量)
csharp 复制代码
// 为指定测点创建时间序列,指定数据类型(FLOAT)和编码方式(GORILLA 适合浮点压缩)
session.CreateTimeseries(
    "root.factory.workshop.temperature",
    TSDataType.FLOAT,
    TSEncoding.GORILLA,
    CompressionType.SNAPPY
);

核心参数说明

参数 含义 常用值
path 序列全路径 例:"root.factory.workshop.temperature"
dataType 物理数据类型 BOOLEAN, INT32, INT64, FLOAT, DOUBLE, TEXT
encoding 编码压缩方式 浮点推荐 GORILLA,整数推荐 TS_2DIFF,文本推荐 PLAIN
compressionType 通用压缩算法 SNAPPY(速度快),GZIP(压缩率高),LZ4UNCOMPRESSED

5.2 写入数据 (Insert & Batch Insert)

5.2.1 单点写入
csharp 复制代码
// 向一个时间序列写入单个数据点(时间戳单位为毫秒)
session.InsertRecord(
    "root.factory.workshop.temperature",
    1680000000000L,   // 时间戳:2023-03-28 18:13:20.000
    25.5f,            // 当前温度值
    false             // false 表示不按对齐时序插入
);
5.2.2 批量写入(高效推荐方式)
csharp 复制代码
var deviceIds = new List<string> {
    "root.factory.workshop.temperature",
    "root.factory.workshop.humidity"
};

var timestamps = new List<long> {
    1680000001000L,
    1680000002000L,
    1680000003000L,
    1680000004000L,
    1680000005000L
};

var valuesList = new List<List<object>> {
    new List<object> { 25.5f, 26.1f, 26.3f, 26.0f, 25.8f },
    new List<object> { 60.1f, 61.0f, 61.5f, 60.8f, 60.3f }
};

var typesList = new List<List<TSDataType>> {
    Enumerable.Repeat(TSDataType.FLOAT, 5).ToList(),
    Enumerable.Repeat(TSDataType.FLOAT, 5).ToList()
};

// 批量插入多条记录(大幅提升写入吞吐量)
session.InsertRecords(deviceIds, timestamps, valuesList, typesList);
5.2.3 使用 Tablet 工具(最高性能写入)

Tablet 是 IoTDB 提供的一种高效数据载体,适合设备下多个测点具有相同时间戳序列的场景,可实现每秒数千万数据点的写入性能:

csharp 复制代码
var schema = new Dictionary<string, TSDataType> {
    { "temperature", TSDataType.FLOAT },
    { "humidity", TSDataType.FLOAT },
    { "status", TSDataType.BOOLEAN }
};

var tablet = new Tablet("root.factory.workshop", schema, 1000);

// 向 Tablet 中添加数据行
for (int i = 0; i < 1000; i++)
{
    tablet.AddTimestamp(i, 1680000000000L + i * 1000);
    tablet.AddValue("temperature", i, 25.0f + i * 0.1f);
    tablet.AddValue("humidity", i, 60.0f + i * 0.05f);
    tablet.AddValue("status", i, i % 2 == 0);
}

// 一次性提交 Tablet 内所有数据
session.InsertTablet(tablet);

5.3 查询 (Read) --- 三种常用模式

5.3.1 原始数据查询 (Raw Data Query)
csharp 复制代码
// 按时间范围查询原始数据,SessionDataSet 实现了迭代器模式
var dataSet = session.ExecuteRawDataQuery(
    paths: new List<string> { "root.factory.workshop.temperature" },
    startTime: 1680000000000L,
    endTime:   1680003600000L
);

while (dataSet.HasNext())
{
    var record = dataSet.Next();
    long timestamp = record.GetTimestamp();
    float value = record.GetFloat(1);  // 索引 0 为时间戳,1 为值
    Console.WriteLine($"[{timestamp}] temperature = {value}");
}
dataSet.Close();

ExecuteRawDataQuery 方法签名说明:

csharp 复制代码
SessionDataSet ExecuteRawDataQuery(
    List<string> paths,    // 要查询的时间序列路径列表
    long startTime,        // 开始时间戳
    long endTime           // 结束时间戳
)
5.3.2 聚合查询 (Aggregation Query)
csharp 复制代码
// 统计指定时间段内温度的平均值
var aggResult = session.ExecuteAggregationQuery(
    paths: new List<string> { "root.factory.workshop.temperature" },
    aggregations: new List<Aggregation> { Aggregation.AVG },
    startTime: 1680000000000L,
    endTime:   1680003600000L
);

while (aggResult.HasNext())
{
    var row = aggResult.Next();
    Console.WriteLine($"Avg Temp: {row.GetFloat(0)}");
}
aggResult.Close();

常用聚合操作符对照:

枚举值 含义 枚举值 含义
COUNT 计数 SUM 求和
AVG 平均值 MIN_VALUE 最小值
MAX_VALUE 最大值 FIRST_VALUE 最早值
LAST_VALUE 最新值 EXTREME 极值
5.3.3 降采样查询 (Downsampling / Group By Time)
csharp 复制代码
// 每 60 秒计算一次温度平均值,适合趋势展示
var dsResult = session.ExecuteRawDataQuery(
    paths: new List<string> { "root.factory.workshop.temperature" },
    startTime: 1680000000000L,
    endTime:   1680003600000L
);
// 如需更精细的分组控制,可结合 IoTDB SQL 中的 `GROUP BY` 语法

建议 :对于复杂降采样场景,推荐使用原生 SQL 模式,直接执行 SELECT avg(temperature) FROM root.factory.workshop GROUP BY ([start, end), 60s)

5.3.4 原生 SQL 查询 (兼容模式)
csharp 复制代码
// 直接执行 IoTDB 标准 SQL(与命令行语法完全一致)
var sqlResult = session.ExecuteQueryStatement(
    "SELECT temperature, humidity FROM root.factory.workshop " +
    "WHERE time >= 1680000000000 AND time <= 1680003600000 " +
    "ORDER BY time DESC LIMIT 100"
);
while (sqlResult.HasNext())
{
    var row = sqlResult.Next();
    Console.WriteLine($"{row.GetTimestamp()}: {row.GetFloat(1)}, {row.GetFloat(2)}");
}
sqlResult.Close();

5.4 更新 (Update)

时序数据库的更新严格意义上是指**覆盖(Upsert)**操作。

  • 若时间戳已存在,IoTDB 会保留最新值(后插入的值覆盖前值)
  • 若时间戳未被占用,则直接插入新数据点

因此更新与插入使用相同代码,无需额外操作:

csharp 复制代码
// 与 5.2.1 完全相同的调用,若时间戳 1680000000000L 已有值则覆盖
session.InsertRecord(
    "root.factory.workshop.temperature",
    1680000000000L,
    28.0f,
    false
);

5.5 删除 (Delete)

5.5.1 删除单个数据点
csharp 复制代码
// 删除指定序列在指定时间戳的数据点
session.DeleteData(
    "root.factory.workshop.temperature",
    1680000000000L
);

注意:此操作需谨慎执行,IoTDB 会对删除数据生成墓碑标记,物理空间回收存在一定延迟。

5.5.2 删除整个时间序列
csharp 复制代码
// 删除从序列到其下所有子节点的全部数据与元数据
session.DeleteTimeseries("root.factory.workshop.temperature");
5.5.3 删除存储组(级联删除)
csharp 复制代码
// 删除整个存储组及其下全部序列与数据------不可逆操作
session.DeleteStorageGroup("root.factory");

核心 CRUD 操作速查表

操作 核心方法 说明
创建存储组 SetStorageGroup("root.xxx") 必须先于序列创建
创建序列 CreateTimeseries(path, type, enc, comp) 定义测点的数据类型与编码
单点写入 InsertRecord(path, ts, value, false) 简单场景使用
批量写入 InsertRecords(devices, timestamps, values, types) 推荐日常使用
高效写入 InsertTablet(tablet) 极大量数据场景首选
原始查询 ExecuteRawDataQuery(paths, start, end) 按时间范围检索
聚合查询 ExecuteAggregationQuery(paths, aggs, start, end) 统计计算
SQL 查询 ExecuteQueryStatement(sql) 与命令行语法一致
更新 InsertRecord(...) (覆盖) 与写入共用相同方法
删除数据点 DeleteData(path, ts) 单个点精准删除
删除序列 DeleteTimeseries(path) 删除整个序列及历史数据
删除存储组 DeleteStorageGroup("root.xxx") 级联删除,不可逆

6. 注意事项与最佳实践

6.1 写入性能优化

  • 优先批量写入 :单次批量万级以上数据点时,优先使用 InsertRecords;十万级以上数据点时,推荐使用 InsertTablet,性能差距可达数量级。
  • 启用会话池 :多线程并发写入时,通过 SessionPool 共享物理连接,复用 RPC 链路,减少频繁建连开销。
  • 合理设置 FetchSize:查询大结果集时,单次拉取行数不宜过小(建议 ≥ 1000),以降低网络往返次数。

6.2 资源管理

  • 使用 using 语句或手动 Close() :及时释放 SessionDataSet 等非托管资源。
  • 应用退出时关闭连接池 :程序退出前调用 sessionPool.Close(),避免 Thrift 连接残余导致服务端资源泄露。

6.3 时间戳规范

  • IoTDB 内部统一使用长整型毫秒时间戳(Unix 毫秒)。
  • 可通过 DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() 将当前时间转换为毫秒级时间戳。

6.4 查询性能优化

  • 限制时间窗口 :查询时尽量指定精确的 startTimeendTime,避免全表扫描。
  • 善用降采样 :展示历史趋势时,优先在服务端使用聚合 + GROUP BY 完成降采样,再返回少量汇总数据给客户端。

对于工业物联网场景而言,时序数据库的核心优势在于高并发写入性能高效的时序范围扫描能力,合理利用批量写入与降采样查询,可显著降低系统资源消耗并提升数据处理的实时性。

相关推荐
百锦再2 小时前
时序数据库选型指南:大数据时代的“数据基建”与 IoTDB 的工业原生之路
大数据·数据库·mysql·oracle·sqlserver·时序数据库·iotdb
weixin_430750932 小时前
部署FreeRadius+php+apache+mariaDB+daloradius 实现认证计费功能
php·apache·mariadb·daloradius·freeradius
檀越剑指大厂2 小时前
时序数据库选型指南-IoTDB
数据库·时序数据库·iotdb
回忆2012初秋3 小时前
.NET 实战:Redis 缓存穿透、击穿与雪崩的原理剖析与解决方案
redis·缓存·.net
武藤一雄14 小时前
19个核心算法(C#版)
数据结构·windows·算法·c#·排序算法·.net·.netcore
还在忙碌的吴小二1 天前
Apache HertzBeat 安装使用完整指南
apache
web前端神器1 天前
记录uniapp小程序的报错
小程序·uni-app·apache
旡心-小小康1 天前
.NET WebSocket Socket
websocket·网络协议·.net
wenha2 天前
踩坑记录:UTF-8、UTF-8-BOM 与 GB2312 读取的乱码真相
utf-8·.net·编码·utf-8-bom