跳到主要内容

基本概念

为了说明时序数据的基本概念,并为后续示例提供统一的数据模型,本文以智能电表为例介绍 TDengine TSDB(下文简称 TDengine)的数据建模方式。假设某型号智能电表能够采集电流、电压和相位这 3 个模拟量,同时每块智能电表还具有位置和分组等静态属性。

示例数据

采集到的数据示例如下表所示。

Device IDTimestampCurrentVoltagePhaseLocationGroup ID
d1001153854868500010.32190.31California.SanFrancisco2
d1002153854868400010.22200.23California.SanFrancisco3
d1003153854868650011.52210.35California.LosAngeles3
d1004153854868550013.42230.29California.LosAngeles2
d1001153854869500012.62180.33California.SanFrancisco2
d1004153854869660011.82210.28California.LosAngeles2
d1002153854869665010.32180.25California.SanFrancisco3
d1001153854869680012.32210.31California.SanFrancisco2

上表展示了各设备 ID(Device ID)对应的智能电表在特定时刻采集的物理量数据,包括电流(current)、电压(voltage)和相位(phase)。除动态采集的数据外,每块智能电表还带有一组静态标签(tag),例如位置(location)和分组 ID(Group ID)。这些设备可以根据外部触发事件或预设周期持续采集数据,从而形成不断更新的时序数据流。

基本概念

如果你只是想先完成基础建模,可以重点理解采集量、标签、数据采集点、表、超级表和子表。虚拟表适用于更复杂的业务分析场景,可以在后续需要时再深入了解。

采集量

采集量是指通过各种传感器、设备或其他类型的采集点获取的物理量,如电流、电压、温度、压力、GPS 等。由于这些物理量随时间不断变化,采集数据的类型也可能不同,包括整型、浮点型、布尔型和字符串等。随着时间积累,存储的数据会持续增长。以智能电表为例,currentvoltagephase 是典型的采集量。

标签

标签是指附着在传感器、设备或其他类型采集点上的静态属性,这些属性通常不会随时间变化,例如设备型号、颜色、设备所在地等。标签的数据类型可以是任意类型。尽管标签本身是静态的,但在实际应用中,用户可能需要修改、删除或添加标签。与采集量不同,随着时间推移,存储的标签数据量通常保持相对稳定,不会呈现明显增长趋势。在智能电表示例中,locationgroup_id 是典型的标签。

数据采集点

数据采集点是指在一定的预设时间周期内或受到特定事件触发时,负责采集物理量的硬件或软件设备。一个数据采集点可以同时采集一个或多个采集量,但这些采集量都是在同一时刻获取的,并拥有相同的时间戳。对于结构复杂的设备,通常会有多个数据采集点,每个数据采集点的采集周期可能各不相同,它们之间完全独立,互不干扰。以一辆汽车为例,可能有专门的数据采集点用于采集 GPS,有的数据采集点负责监控发动机状态,还有的数据采集点则专注于车内环境监测。这样,一辆汽车就包含了 3 个不同类型的数据采集点。在智能电表示例中,d1001d1002d1003d1004 等标识符代表了不同的数据采集点。

鉴于采集的数据通常是结构化数据,为了降低用户的学习难度,TDengine 采用传统的关系型数据库模型来管理数据。同时,为了充分发挥时序数据的特性,TDengine 采取了“一个数据采集点一张表”的设计策略,即要求为每个数据采集点单独建立一张表。例如,若有千万块智能电表,则在 TDengine 中需要创建相应数量的表。在智能电表示例数据中,设备 ID 为 d1001 的智能电表对应 TDengine 中的一张表,该电表采集的所有时序数据均存储于此表中。这种设计方式既保留了关系型数据库的易用性,又充分利用了时序数据的独特优势。

“一个数据采集点一张表”的设计有几大优点:

  1. 由于不同数据采集点产生数据的过程完全独立,每个数据采集点的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写数据,写入速度能大幅提升。

  2. 对于一个数据采集点而言,其产生的数据按照时间递增,因此写入操作可以通过追加方式实现,进一步提高数据写入速度。

  3. 一个数据采集点的数据以块为单位连续存储。这样,每次读取一个时间段的数据时,可以大幅减少随机读取操作,数量级提升读取和查询速度。

  4. 一个数据块内部采用列式存储。对于不同的数据类型,可以采用不同压缩算法来提高压缩率。同时,由于采集量的变化通常是缓慢的,压缩率会更高。

如果采用传统方式,将多个数据采集点的数据写入一张表,由于网络延时不可控,不同数据采集点的数据到达服务器的时序无法保证,写入操作需要锁保护,而且同一数据采集点的数据也难以保证连续存储在一起。采用一个数据采集点一张表的方式,可以最大程度保证单个数据采集点的插入和查询性能,并提高数据压缩率。

在 TDengine 中,通常使用数据采集点的名称(如 d1001)作为表名。每个数据采集点可以有多个采集量(如 currentvoltagephase 等),每个采集量对应一张表的一列。采集量的数据类型可以是整型、浮点型、字符串等。

此外,表的第一列必须是时间戳,即数据类型为 timestamp。对于每个采集量,TDengine 将使用第一列时间戳建立索引,并采用列式存储。对于汽车等复杂设备,由于它有多个数据采集点,因此需要为一辆汽车建立多张表。

超级表

TDengine 采用“一个数据采集点一张表”的设计虽然有利于高效地管理每个数据采集点,但随着设备数量不断增加,表的数量也会急剧增加,这给表的管理以及表之间的聚合带来了巨大的挑战。

为了解决这个问题,TDengine 引入超级表(Super Table,简称 STable)的概念。超级表是一种数据结构,它能将某一特定类型的数据采集点聚集在一起,形成一张逻辑上的统一表。这些数据采集点具有相同的表结构,但各自的静态属性(如标签)可能不同。创建超级表时,除了定义采集量的结构之外,还需定义超级表的标签。一张超级表至少包含一个时间戳列、一个或多个采集量列以及一个或多个标签列。此外,超级表的标签可以灵活地进行增加、修改或删除操作。

在 TDengine 中,表代表具体的数据采集点,而超级表则代表一组具有相同属性的数据采集点集合。以智能电表为例,可以为该类型的电表创建一张超级表,其中包含所有智能电表的共有属性,包括动态的时序数据以及静态的标签数据。这种设计不仅简化了表的管理,还便于进行跨数据采集点的聚合操作,从而提高数据处理效率。

子表

子表是数据采集点在逻辑上的一种抽象表示,它是隶属于某张超级表的具体表。用户可以将超级表的定义作为模板,并通过指定子表的标签值来创建子表。这样,通过超级表生成的表便被称为子表。超级表与子表之间的关系主要体现在以下几个方面。

  • 一张超级表包含多张子表,这些子表具有相同的表结构,但标签值各异。
  • 子表的表结构不能直接修改,但可以修改超级表的列和标签,且修改对所有子表立即生效。
  • 超级表定义了一个模板,自身并不存储任何数据或标签信息。

在 TDengine 中,查询操作既可以在子表上进行,也可以在超级表上进行。针对超级表的查询,TDengine 将所有子表中的数据视为一个整体,首先通过标签筛选出满足查询条件的表,然后在这些子表上分别查询时序数据,最终将各张子表的查询结果合并。本质上,TDengine 通过对超级表查询的支持,实现了多个同类数据采集点的高效聚合。为了更好地理解采集量、标签、超级表与子表之间的关系,这里以智能电表的数据模型为例进行说明。可以参考下图的数据模型,以便更直观地了解这些概念。

数据模型示意图

虚拟表

“一个数据采集点一张表”以及“超级表”的设计解决了工业和物联网等场景下的大多数时序数据管理和分析难题。但在真实场景中,一个设备往往有多种传感器,数据采集频次也可能相差很大。例如,对于一台风机,电气参数、环境参数、机械参数各自对应的传感器和采集频次可能完全不同。因此,很难用一张真实表描述一台设备,往往需要多张表。当需要综合多个传感器的数据进行分析计算时,只能通过多级关联查询实现,这可能带来易用性和性能问题。从用户角度看,“一个设备一张表”更直观,也更容易操作。但如果在建模之初直接采用“一个设备一张表”的设计,由于采集频次不同,每一个具体时间戳下都可能存在大量空值列,从而降低存储和查询效率。

为了解决这个问题,TDengine 引入虚拟表(Virtual Table,简称 VTable)的概念。虚拟表不存储实际数据,但可以用于分析计算。它的数据来源于真实存储数据的子表或普通表,并通过按时间戳排序、对齐和合并各原始表中的不同列生成。与真实表类似,虚拟表也可以分为虚拟超级表、虚拟子表和虚拟普通表。虚拟超级表可以表示一个设备或一组分析计算所需数据的完整集合,每个虚拟子表可以根据需要引用相同或不同的列,因此可以按业务视角灵活定义。虚拟表不能写入或删除数据,查询方式与真实表相同。TDengine 支持在虚拟超级表、虚拟子表和虚拟普通表上执行查询。区别在于,虚拟表的数据在每次查询时动态生成,只有查询中引用的列才会被合并进虚拟表,因此同一个虚拟表在不同查询中呈现和扫描的数据可能完全不同。

虚拟表机制让 先写入后建模 成为现实。在数据采集和写入阶段,无需预先为复杂业务分析设计表结构,可以按照最接近设备协议的方式(如单列模型)直接写入 TDengine。待数据存储后,再根据实际分析需求,通过虚拟表动态创建适合业务视角的数据模型。这可以降低数据接入复杂度和初期建模负担,使数据处理流程更加灵活高效。

虚拟超级表的主要功能特点包括:

  1. 列选择与拼接:用户可以从多个原始表中选择指定的列,按需组合到一张虚拟表中,形成统一的数据视图。
  2. 基于时间戳对齐:以时间戳为依据对数据进行对齐,如果多个表在相同时间戳下存在数据,则对应列的值组合成同一行;若部分表在该时间戳下无数据,则对应列填充为 NULL。
  3. 动态更新:虚拟表根据原始表的数据变化自动更新,确保数据的实时性。虚拟表不需要实际存储,计算在生成时动态完成。

通过引入虚拟表,TDengine 可以更方便地管理更大、更复杂的设备数据。无论每个采集点采用单列模型还是多列模型,也无论这些采集点的数据分布在一个库还是多个库中,都可以通过定义虚拟表跨库、跨表指定数据源,并通过虚拟超级表进行跨数据采集点的聚合运算,从而实现“一个设备一张表”的分析视角。

库是 TDengine 中用于管理一组表的集合。TDengine 允许一个运行实例包含多个库,并且每个库都可以配置不同的存储策略。由于不同类型的数据采集点通常具有不同的数据特征,例如数据采集频率、数据保留期限、副本数量、数据块大小等,为了在各种场景下确保 TDengine 能够发挥最大效率,建议将具有不同数据特征的超级表创建在不同的库中。

在一个库中,可以包含一到多张超级表,但每张超级表只能属于一个库。同时,一张超级表所拥有的所有子表也都将存储在该库中。这种设计有助于实现更细粒度的数据管理和优化,确保 TDengine 能够根据不同数据特征提供最佳的处理性能。

时间戳

时间戳在时序数据处理中扮演着至关重要的角色,特别是在应用程序需要从多个不同时区访问数据库时,这一问题变得更加复杂。在深入了解 TDengine 如何处理时间戳与时区之前,先介绍以下几个基本概念。

  • 本地日期时间:指特定地区的当地时间,通常表示为 yyyy-MM-dd hh:mm:ss.SSS 格式的字符串。这种时间表示不包含任何时区信息,如 2021-07-21 12:00:00.000
  • 时区:指地球上不同地理位置的标准时间。协调世界时(Universal Time Coordinated,UTC)或格林尼治时间是国际时间标准,其他时区通常表示为相对于 UTC 的偏移量,如 UTC+8 代表东八区时间。
  • UTC 时间戳:表示自 UNIX 纪元(即 UTC 时间 1970 年 1 月 1 日 0 点)起经过的毫秒数。例如,1700000000000 对应的日期时间是 2023-11-14 22:13:20(UTC+0)

在 TDengine 中保存时序数据时,实际保存的是 UTC 时间戳。TDengine 在写入数据时,时间戳的处理分为以下两种情况:

  • RFC-3339 格式:当使用这种格式时,TDengine 能够正确解析带有时区信息的时间字符串为 UTC 时间戳。例如,2018-10-03T14:38:05.000+08:00 会被转换为 UTC 时间戳。
  • 非 RFC-3339 格式:如果时间字符串不包含时区信息,TDengine 将使用应用程序所在的时区设置自动将时间转换为 UTC 时间戳。

在查询数据时,TDengine 客户端会根据应用程序当前的时区设置,自动将保存的 UTC 时间戳转换成本地时间进行显示,确保用户在不同时区下都能看到正确的时间信息。

继续阅读

理解这些概念后,可以继续阅读 数据建模,使用 SQL 创建数据库、超级表、子表和虚拟表。