现在系统中有一张表存了设备的各种运行状态,比如 CPU 温度、使用率、负载等,以及一些埋点事件信息,这些其实归纳起来都可以作为 metric 来进行收集,业界现在的答案基本就是 Prometheus。而 Prometheus 底层的存储就是一个时序数据库,所以很自然的,我们也想将这些数据存储到一个时序数据库中。首先是想到了 TSDB,本来阿里云也有相关的产品,但后来停止维护了,提供了 Lindorm 的时序引擎实现。所以就了解了下 Lindorm 的时序引擎,这个引擎实现了多值模型,看起来可以很好的替换 TSDB,但是后来折腾下来发现,并不适合我们的业务模型。
数据模型
时序数据元素说明
Lindorm 时序引擎相比 OpenTSDB,在概念上还是有一些不同,比如在 OpenTSDB 中的 Metric 概念,在 Lindorm 时序引擎中就不存在了,咨询了下同学,应该就是对应 Lindorm 中表的概念,但是感觉与官网中的介绍又不大相同。其他的基本类似。
元素 | 描述 |
---|---|
表名(Table) | 时序表代表一系列同类时序数据的集合,例如为空气质量传感器建立一个Table,存储所有传感器的监测数据。 |
标签(Tag) | Tag描述数据源的特征,通常不随时间变化,例如传感器设备,包含设备DeviceId、设备所在的Region等Tag信息,数据库内部会自动为Tag建立索引,支持根据Tag来进行多维检索查询。Tag由Tag Key、Tag Value组成,两者均为String类型。 在定义时序数据表时,还可以指定特定标签列作为Primary Key来显式指定数据在多个节点上分片(Data Sharding)时的分片规则,从而实现贴合业务场景的性能提升。 |
时间戳(Timestamp) | Timestamp代表数据产生的时间点,可以写入时指定,也可由系统自动生成。 |
量测值(Field) | Field描述数据源的量测指标,通常随着时间不断变化,例如传感器设备包含温度、湿度等Field,Field列无需提前创建固定的Schema,可以在运行过程中动态增减;Field由Field名和Field值组成,Field名为字符串类型,Field值支持多种数据类型。 |
数据点(Data Point) | 数据源在某个时间产生的某个量测指标值(Field值)称为一个数据点,数据库查询、写入时按数据点数来作为统计指标。 |
时间线(Time Series) | 数据源的某一个指标随时间变化,形成时间线,Tag的组合确定一条时间线。针对时序数据的计算包括降采样、聚合(sum、count、max、min等)、插值等都基于时间线维度进行; 数据库在存储数据时,会将同一条时间线的数据尽量聚类存储,提升时间线数据访问效率,同时更好的支持时序数据压缩。 在时序表中,Tag列的值都相同的一系列数据行构成了一条时间线。 |
时间线
例如:某风力电厂包含一系列的智能风力发电机设备,创建名为 Wind-generators 的 table 来存储所有设备的信息,设备由 ID、型号、厂商等 Tag 信息描述,每个设备会持续上报功率、风速等 Field 指标,指标数据通过时序数据库的 API 实时写入到云端时序数据库。
Schema 约束
与传统关系型数据库不一样,Lindorm 时序引擎提供三种不同的 Schema 约束方式,以支持业务的快速变化。
- 强约束
与传统关系型数据库类似,时序引擎会严格依据预先定义的表结构对写入数据的表名、字段名、类型进行校验。不符合即出错。 - 弱约束
写入数据的所属表不存在时引擎不会报错,而是会自动创建对应的表。
写入数据的新增一个标签或字段时,引擎不会校验报错,而是会在对应的时序数据表中自动添加一个TAG列或FIELD列。
写入数据的相同字段的数据类型发生变化时时序引擎会检测到字段的数据类型不匹配而写入失败,需要使用ALTER TABLE语句手动修改字段的数据类型。 - 无约束
不做任何约束。代价是无法直接通过SQL查询写入的数据。
数据写入方式与Schema约束的选择
写入方式 | Schema约束策略 | 备注 |
---|---|---|
通过JDBC API写入数据 | 强约束 | 不涉及。 |
通过教程:通过Java Native SDK连接并使用Lindorm时序引擎写入数据 | 可选项。支持强约束和弱约束。 | 策略配置方法请参见Java Native SDK使用手册。 不配置时的默认约束是弱约束。 |
通过api/v2/sql使用INSERT写入数据 | 强约束 | 不涉及。 |
通过行协议写入数据 | 可选项。支持强约束和弱约束。 | 策略配置方法请参见行协议写入。 不配置时的默认约束是弱约束。 |
使用约束
数据库对象 | 说明 |
---|---|
database名称 | 由大写字母、小写字母、数字、下划线其中的一种或多种组成,长度为2~64个字符。 |
database数量 | 一个实例最多创建16个数据库(包含default数据库)。 |
table名称 | 由大写字母、小写字母、数字、下划线其中的一种或多种组成,最大长度为128个字符。 |
table数量 | 一个数据库最多创建10000个数据表。 |
tag列名称 | 可见字符,最大长度为200个字符。 |
tag列值 | 可见字符,最大长度为20480个字符。 |
field列名称 | 可见字符,最大长度为200个字符。 |
field列值 | 可见字符,最大长度为20480个字符。 |
timestamp列名称 | 可见字符,最大长度为200个字符,一张表只能有一个timestamp列,默认作为时间索引。 |
timestamp列值 | 单位为毫秒。 |
列数量 | 一张表最多可创建1024列,其中包含tag列、timestamp列、field列。 |
使用说明
由上面的介绍可以看出来 Lindorm 时序引擎更像是一张普通的 MySql 表,只是会自动在 Tag 列上加索引。我们现在系统中存储的指标项已经有 200+,如果按照这种模型来建表,意味着需要建一个超过 200 列的表。虽然数据库限制上是支持的(最大 1024 列),并且 Native SDK 还支持动态列,也不需要预先 DDL。但是在使用上实在是个难题,难道也建一个 200+ 字段的对象进行映射吗?
最初想到的解决方案是在 Tag 列中加一个 metric 字段,用来存储各种指标的名称,这样跟现在的数据库存储模型基本一致。但咨询过 Lindorm 的同学,告知每单位 CPU 最大支撑同时 10w 时间线 (推荐 4w 时间线)。而我们现在有 2W 设备,如果将 metirc 作为 Tag,意味着将有超过 400W 时间线,这个成本有点高。由于绝大多数查询是通过设备 ID + metric + ts,所以 metric 也无法作为普通的 Field 列做存储,而且同一时间可能有多个 metric。
看官网上的介绍,对 TSDB 的兼容,是通过 table 与 metric 做对应的。这个实际操作起来太麻烦,首先表的数量太多,其次就是后续表数据同步都是问题。
其实对监控、指标收集类型的业务来说,还是 TSDB 这样的模型比较适合。因为指标项有可能很多,而且随着业务的扩展也会不断增加。Lindorm 的时序引擎,也支持单值模型,但是看官网上的介绍,应该是支持有限也不推荐使用,所以也就没有再去尝试使用。
Lindorm 的时序引擎选择的这个多值模型,我猜想可能是跟底层使用列式存储有关系。这个多值模型适合于有少量固定列的情况,相比较 MySql 数据库提供更加高效的写入能力,提供了降采样、插值等高级能力。其他基本与 MySql 模型没有差别。