InfluxDB 数据模型
目录
本节的目标是为读者提供 InfluxDB 数据模型的坚实基础,特别是
- 理解 InfluxDB 输入格式(线路协议)。
- 理解 InfluxDB 输出格式(带注释的 CSV)。
- 这两种格式之间的关系。
- 理解 InfluxDB 存储引擎如何将数据持久化到磁盘上的表中
InfluxDB 数据元素
桶
InfluxDB 中的所有数据都会写入到一个桶中。一个 桶 是一个容器,可以容纳任意数量测量的点。桶有一些重要的属性:
- 它们可以根据您的需要命名(在合理的范围内)。
- 您可以创建令牌来控制桶的读写权限,仅限于特定桶。
- 您必须在创建桶时设置保留期限。保留期限 决定 InfluxDB 在该桶中存储时间序列数据的时长。保留期限对于时间序列数据库管理至关重要。它们为用户提供了一个方便的解决方案,可以自动过期旧的、无用的数据,从而使用户能够专注于最近的、有价值的数据,同时降低存储成本。
这些主题将在后面的章节中详细介绍,现在只需知道测量值存储在桶中并从桶中读取即可。
测量
测量 是桶内最高级别的数据结构。InfluxDB 每个点接受一个测量值。使用测量来组织类似的数据。在某些方面,您可以将其视为类似于传统数据库中的表。测量值也被索引,这使您在过滤特定测量值时可以更快地查询测量值中的数据。测量值必须是字符串类型。测量名称不能以下划线开头。
为了进一步理解测量,假设您正在构建一个天气应用程序,并将多个城市的气温数据写入 InfluxDB。对于这个时间序列用例,您可以创建一个名为“air_temperature”的测量。
标签集
标签集 由键值对组成,其中值始终是字符串。标签本质上是元数据,通常对数据的来源进行编码。
假设您正在构建一个天气应用程序,并将多个城市的气温数据写入 InfluxDB。对于这个时间序列用例,您可以向数据中添加一个名为“location”的标签键,其中“location”标签键包含您正在监控天气城市的标签值。这样,您可以轻松地查询“洛杉矶”的所有温度数据,例如,通过过滤具有“洛杉矶”标签值的“location”标签键。
至关重要的是,标签已编入索引,因此它们是设计查询性能的重要元素。但是,标签也是可选的。标签键不能以下划线开头,因为 InfluxDB 保留前导下划线供自己使用。
字段集
字段集 由键值对组成,其中值可以是字符串、整数或浮点数。字段是要存储、可视化、用于计算等的实际数据。
继续以天气应用程序为例,温度读数就是字段的一个示例。
InfluxDB 需要字段集。这是您存储时间序列数据值的地方。字段值可以是整数、浮点数或字符串类型。由于字段值几乎总是不同的,因此字段键通常简称为字段。字段未编入索引。字段也不能以下划线开头。
序列和数据点
序列由测量、标签集和字段的唯一组合定义。数据点是特定时间戳下序列中的一个数据点。如果您使用相同的测量、标签集、字段和时间戳值重写重复的数据点,则此写入将覆盖您之前的数据点。如果您尝试将新数据点写入同一序列,但更改了字段类型,则此写入将失败。
假设和约定
在深入探讨有关 InfluxDB 数据模型的更细微和更专业的主题之前,让我们花点时间建立一些基线假设和约定。
本书使用的约定
本书中的章节通常会使用抽象和简化的示例介绍概念,然后使用真实世界的数据进行详细示例。
示例的元语法
对于抽象和简化的示例,我们将使用以下形式的名称
attributeorvaluen
其中“attributeorvalue”指的是表的列名或点中的值,“n”是一个数字,用于区分相同角色的多个标识符。角色可以包含以下任何一项
- 测量
- 标签
- 标签值
- 字段
示例通常还包括
- 字段值
- 时间戳
字段值将由实际值表示。时间戳采用以下时间戳格式
- Unix:Unix 时间戳是一种将时间作为秒的累积总数进行跟踪的方法,例如
1465839830100400200
- RFC3339:互联网工程任务组 (IETF) 的征求意见文档中日期和时间表示的标准,例如
2019-08-28T22:00:000000000Z
- 相对持续时间:-
1h
- 持续时间:
1h
时间戳将由实际值或 unixtime1
或 rfc3339time1
表示。如果我们想在同一个示例中引用另一个时间戳,我们将使用 unixtime2
或 rfc3339time2
。
要在示例中引用测量,我们将使用:measurement1
。如果我们想在同一个示例中引用另一个测量,我们将使用 measurement2
,依此类推。
线路协议(稍后将详细解释)的示例可能如下所示
measurement1,tag1=tagvalule1,tag2=tagvalue2 field1=1i,field2=2 1628858104
有时,示例可能侧重于理解类型。在这种情况下,我们将使用“atype”形式,其中“type”是所关注的数据类型。例如,如果我们正在讨论字段名称始终是字符串,我们可能会说
r._field == "astring"
或者,如果我们正在讨论类型冲突,我们可能会说
aint == afloat
而不是带有特定值的示例,例如
1i == 1.0
IOx 数据模型介绍
InfluxDB 正在使用新的 *IOx 数据模型* 进行重大升级。
阅读本节可以充分理解使用 IOx 桶的新 InfluxDB 数据模型,尤其要注意 *IOx 数据模型* 与 *TSM 数据模型* 的区别。
**注意:**IOx 仍在测试中。请继续关注 influxdata.com 以了解我们何时正式将新的 IOx 数据模型推广到所有 InfluxDB Cloud 生产集群。
TSM 和 IOx 数据模型之间的相似之处
数据元素
IOx 保留了用户在 InfluxDB 中习惯使用的相同数据元素,即
- 用于存储数据的桶。
- 用于将类似数据分组在一起的测量。
- 标签集,与时间戳一起,标识表中的唯一行。
- 用于存储实际数据的字段。
- 当然,还有用于按时间维度对数据进行排序的时间戳。
这些元素的当前文档可在此处此处获取,并将更新以反映与 IOx 相关的任何更改。
Flux 兼容性
拥有现有 Flux 脚本并且这些脚本运行良好的用户可以放心,这些脚本将继续有效,无需修改。在 IOx 的整个开发过程中,这种向后兼容性一直是一个重点。但是,用户应该注意,通过对其 Flux 进行一些小的更改(稍后将进行描述),他们将能够使用 IOx 实现显著的性能改进。
线路协议兼容性
与 Flux 类似,我们投入了大量精力以确保保留 InfluxDB 行协议兼容性。因此,此处提供的有关行协议的文档仍然适用,但需要注意的是,如果用户想要充分利用 IOx,则应以不同的方式考虑磁盘持久性和使用 Table Flux 的输出格式。本文档将从那里继续,介绍将数据持久化到磁盘的模型。
从行协议到磁盘上的表
在以前版本的 InfluxDB 中,将数据库设想为按序列存储数据最为实用,每个序列都在一个单独的表中。IOx 数据模型可以说更直观,因为它构建了 Table Flux 返回的表。
与之前的 TSM 一样,IOx 是一个“写入时模式”数据库引擎。这意味着您可以在部署应用程序后随意写入带有新测量值、标签、标签值和字段的数据,IOx 将接受这些写入并将它们持久化为表。请注意,关于写入时模式有一些重要的注意事项,例如,您不能仅通过写入具有新类型的值来更改字段的类型。
IOx 中的表由测量名称定义。表中的列包括
- 标签名称
- 字段名称
- 单个时间列
因此,行包含
- 标签值
- 字段值
- 单个时间戳
行由其标签值和时间戳标识。这在理解 Upserts 时变得很重要。
在以下示例中,我们将探讨如何在 IOx 中通过写入创建表。
行协议、字段和表
最简单的行协议是一个测量值和一个带有值的字段。如果省略时间戳,我们可以允许数据库通过从行协议中省略它来添加时间戳
measurement1 field1=1i
这将由 IOx 持久化为一个表
名称:measurement1 | |
field1 | time |
1i | timestamp1 |
如您在上面的示例中看到的,该表由测量名称定义,并包含一个名为“field1”的列和一行数据。写入更多类似的数据将按预期扩展表。如果我们再写一行行协议
measurement1 field1=2i
名称:measurement1 | |
field1 | time |
1i | timestamp1 |
2i | timestamp2 |
如果我们写入第三行行协议,这次使用不同的字段名称,IOx 会将该字段添加到表中,并将之前的写入值设为空值。
measurement1 field2=3i
名称:measurement1 | ||
field1 | field2 | time |
1i | null | timestamp1 |
2i | null | timestamp2 |
null | 3i | timestamp3 |
当然,IOx 仍然支持在单行行协议中写入多个字段,因此我们可以编写如下行协议
measurement1 field1=4i,field2=4i
名称:measurement1 | ||
field1 | field2 | time |
1i | null | timestamp1 |
2i | null | timestamp2 |
null | 3i | timestamp3 |
4i | 4i | timestamp4 |
添加标签
从表面上看,标签和字段在 IOx 中似乎是等效的。例如,一段带有单个标签和字段的简单行协议将生成一个包含 3 列的表,一列用于标签,一列用于字段,一列用于时间。考虑以下行协议和生成的表。
measurement1,tag1=tagvalue1 field1=1i
名称:measurement1 | ||
field1 | tag1 | time |
1i | tagvalue1 | timestamp1 |
与之前的数据模型不同,添加新的标签值会被添加到同一个表中
measurement1,tag1=tagvalue2 field1=2i
名称:measurement1 | ||
field1 | tag1 | time |
1i | tagvalue1 | timestamp1 |
2i | tagvalue2 | timestamp2 |
现在,如果我们进行新的写入,但使用不同的标签名称,类似于添加字段,这将使用标签的新列更新表,并且缺少的标签值将设置为 null。
measurement1,tag2=tagvalue3 field1=3i
名称:measurement1 | |||
field1 | tag1 | tag2 | time |
1i | tagvalue1 | null | timestamp1 |
2i | tagvalue2 | null | timestamp2 |
3i | null | tagvalue3 | timestamp3 |
我们可以通过根据需要引入新的标签和字段,以这种方式继续添加到 measurement1 表中。
measurement1,tag1=tagvalue1,tag2=tagvalue3,tag3=tagvalue4 field1=4i,field2=true
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
仍然可以写入最小的行协议,并将其添加到表中
measurement1 field1=1i
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
1i | null | null | null | null | timestamp5 |
时间戳
如上所述,行由时间戳和标签值的组合标识。因此,只要标签值不同,重复的时间戳就是有效的。例如,我们可以通过改变标签值来添加多个具有 timestamp5 的行。考虑以下行协议,它具有重复的时间戳。
measurement1,tag1=tagvalue1 field1=1i timestamp5
记住一行由其标签值定义,尽管时间戳和字段相同,但这仍然代表一个新行
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
1i | null | null | null | null | timestamp5 |
1i | null | tagvalue1 | null | null | timestamp5 |
当您发送一行行协议时,写入会考虑**整个**标签值集。只要单个标签值不同,即使所有字段和时间戳都相同,写入仍将导致添加新行。
例如,以下行与现有行相同,只是 tag3 的值为 tagvalue5 而不是 tagvalue4。因此,这将导致添加新行。
measurement1,tag1=tagvalue1,tag2=tagvalue3,tag3=tagvalue5 field1=4i,field2=true timestamp4
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
4i | true | tagvalue1 | tagvalue3 | tagvalue5 | timestamp4 |
1i | null | null | null | null | timestamp5 |
1i | null | tagvalue1 | null | null | timestamp5 |
Upserts(更新插入)
将对 InfluxDB 的所有写入视为 Upserts 很有用。“Upsert”一词的意思是写入时“更新或插入”。如果它与现有记录匹配,它将更新;如果没有匹配的现有记录,它将插入新记录。
由于 Upserts 匹配时间戳和标签值,因此无法更新标签值!只有字段值可以更新。
这行行协议不包含标签,具有重复的时间戳和重复的字段名称,但字段值不同。因此,这与时间戳和标签集(恰好为空)匹配。因此,这将导致更新表。
measurement1 field1=2i timestamp5
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
4i | true | tagvalue1 | tagvalue3 | tagvalue5 | timestamp4 |
2i | null | null | null | null | timestamp5 |
1i | null | tagvalue1 | null | null | timestamp5 |
以下行协议也匹配现有时间戳和标签集,因此将导致更新。虽然行协议中只存在一个字段,但行仍然匹配,因此该字段将被更新。
measurement1,tag1=tagvalue1,tag2=tagvalue3,tag3=tagvalue5 field2=false timestamp4
名称:measurement1 | |||||
field1 | field2 | tag1 | tag2 | tag3 | time |
1i | null | tagvalue1 | null | null | timestamp1 |
2i | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | tagvalue3 | null | timestamp3 |
4i | true | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
4i | false | tagvalue1 | tagvalue3 | tagvalue5 | timestamp4 |
2i | null | null | null | null | timestamp5 |
1i | null | tagvalue1 | null | null | timestamp5 |
可以类似地引入新字段。以下行协议匹配现有行的时间戳和所有标签值,因此该行将被更新,以及新的字段值。正如预期的那样,所有没有新字段的现有行的该字段都将设置为 null。
measurement1,tag1=tagvalue1,tag2=tagvalue3,tag3=tagvalue5 field3=0.0 timestamp4
名称:measurement1 | ||||||
field1 | field2 | field3 | tag1 | tag2 | tag3 | time |
1i | null | null | tagvalue1 | null | null | timestamp1 |
2i | null | null | tagvalue2 | null | null | timestamp2 |
3i | null | null | null | tagvalue3 | null | timestamp3 |
4i | true | null | tagvalue1 | tagvalue3 | tagvalue4 | timestamp4 |
4i | false | 0.0 | tagvalue1 | tagvalue3 | tagvalue5 | timestamp4 |
2i | null | null | null | null | null | timestamp5 |
1i | null | null | tagvalue1 | null | null | timestamp5 |