InfluxDB 2.0
特点
- 基于时间序列,支持与时间有关的相关函数(如
window()
,mean()
,rate()
等); - 可度量性:你可以实时对大量数据进行计算;
- 无结构(无模式):可以是任意数量的列;
- 支持min, max, sum, count, mean, median 等一系列函数;
- 内置http支持,使用http读写;
强大的类SQL语法;函数式语言Flux- 自带管理界面,方便使用
数据模型
Timestamp
InfluxDB中存储的所有数据都有一个存储时间戳的_time
列。在磁盘上,时间戳以纳秒格式存储。InfluxDB格式时间戳以[RFC 3339](https://tools.ietf.org/html/rfc3339)
(如:2020-01-01T00:00:00.00Z
)格式显示与数据关联的日期和时间。
Measurement
度量名称,字符串类型,充当标记、字段和时间戳的容器。
Fields
必需,非索引。
Field key
字段键是表示字段名称的字符串。
Field value
字段值表示关联字段的值。值的类型为string, float, integer, uInteger, boolean.
Field set
字段集是与时间戳关联的字段键值对的集合。
Tags
可选,索引。
Tag key
索引键
Tag value
索引值
Tag set
tag key 和 tag value 组成的 set 集合
Series
Series = Measurement + Tag set + Field key
Point
Point = Series + Field value + Timestamp
Bucket
所有的数据都存储在一个Bucket中。Bucket结合了Database和Retention period(每个数据点过期的时间)的概念。Bucket 属于一个organization。
Organization
一组 dashboard,task, bucket 和 users 属于一个 organization.
物理模型
Write Ahead Log (WAL)
当存储引擎收到写入请求时:
- 在WAL文件中追加一条写入操作
- 数据以
fsync()
写入磁盘 - 更新内存中的
Cache
- 数据成功写入磁盘,返回响应
WAL
文件的内容与内存中的Cache
相同,其作用是为了持久化数据(防止数据丢失),当系统崩溃后可以通过WAL
文件恢复还没有写入到TSM
文件中的数据。
Cache
Cache是wal文件在内存中的副本。特点如下:
- 按照
Series
组织数据并存储在其自己的时间顺序范围内 - 存储未压缩的数据
- InfluxDB启动时,会遍历所有的
WAL
文件,重新构造Cache
,即使系统出现故障,也不会导致数据丢失。 - 插入数据时,是往
Cache
与WAL
中写入数据,可以认为Cache
是WAL
文件中的数据在内存中的缓存 - 查询:对存储引擎的查询将
Cache
中的数据与TSM
文件中的数据合并。查询在查询处理时对从Cache
生成的数据副本执行。写数据不影响查询的结果。
Cache
中的数据不是无限增长的,有一个 maxSize 参数(默认上限为25MB)用于控制当Cache
中的数据占用多少内存后就会将数据写入 TSM
文件。每当 Cache
中的数据达到阀值后,会将当前的 Cache
进行一次快照,之后清空当前Cache
中的内容,再创建一个新的WAL
文件用于写入,剩下的WAL
文件最后会被删除,快照中的数据会经过排序写入一个新的TSM
文件中。
Time-Structured Merge Tree (TSM)
为了有效地压缩和存储数据,存储引擎按Series
对字段值进行分组,然后按时间对这些字段值进行排序。
存储引擎使用TSM存储数据,TSM
文件以列格式存储压缩的序列数据。为了提高效率,存储引擎只存储一系列值之间的差异(或增量)。
单个TSM
file大小最大为2GB,用于存放数据。
Time Series Index (TSI)
一种服务机制,随着数据增长,保证一定的查询效率。每隔1秒会检查一次是否有需要压缩合并的数据。主要进行两种操作:
Cache
中的数据大小达到阀值后,进行快照,之后转存到一个新的TSM
文件中- 合并当前的
TSM
文件,将多个小的TSM
文件合并成一个,使每一个文件尽量达到单个文件的最大大小,减少文件的数量,并且一些数据的删除操作也是在这个时候完成。
文件结构
Engine path
- /data:存储TSM文件
- /wal:存储WAL文件
Blot Path
Boltdb数据库的存储路径,存储格式是 Go 的key-value,数据是非时间序列的,包括InfluxDB user, dashboard task等等。
Shard & Shard group
Shards
shard包含由Shard group duration定义的给定时间范围内编码压缩的时间序列数据。在指定的shard group duration内,同一 Series 的所有 Point 都存储在同一个shard中。单个shard包含多个series、磁盘上的一个或多个TSM文件,并且属于一个Shard group。
Shard group
一个shard group 属于一个 bucket,包含由shard group duration定义的特定时间范围的 series 。
Shard group duration
指定每个shard group的时间范围,并确定创建新shard group的频率
Shard group diagram
下图表示一个4天保留时间,shard group持续时间为1天的bucket
Shard life-cycle
Shard precreation
InfluxDB shard precreation服务根据shard组持续时间为每个shard组预先创建具有未来开始和结束时间的shard。precreator服务不会为过去的时间范围预创建碎片。在回填历史数据时,InfluxDB会根据需要为过去的时间范围创建碎片,从而暂时降低写入吞吐量。
Shard writes
InfluxDB 将时间序列数据写入未压缩或 "hot" shard 。当一个 shard 长时间不被写入时,InfluxDB会压缩 shard 数据,从而产生 "cold" shard。
通常,InfluxDB会将数据写入最近的 shard group("hot" shard),但在回填历史数据时,InfluxDB 会将数据写入先解压缩的旧碎片。回填完成后,InfluxDB 重新压缩旧碎片。
Shard compaction
InfluxDB 有以下四个压缩级别:
- L1:InfluxDB 将内存Cache中保存的所有新写入的数据刷新到磁盘
- L2:InfluxDB 通过将 L1 产生的包含相同series的多个块组合到一个或多个新文件中的较少块中
- L3:InfluxDB 遍历 L2 产生压缩文件块(超过一定大小),并将包含相同 series 的多个块组合到新文件中的一个块中。
- L4:完全压缩,InfluxDB 对 L3 产生的压缩文件块进行遍历,并将包含相同 series 的多个块合并到新文件中的一个块中。
Shard deletion
InfluxDB 的 retention enforcement service 定期检查早于其存储桶保留期的 shard group。一旦 shard group 的开始时间超过bucket的保留期,InfluxDB 就会删除 shard group 以及关联的 shard 和 TSM 文件。
优化策略
- 控制series的数量;
- 使用批量写;
- 使用恰当的时间粒度;
- 存储的时候尽量对 Tag 进行排序;
- 根据数据情况,调整shard的 duration;
- 无关的数据写不同的 bucket;
- 控制Tag Key与Tag Value值的大小;
- 存储分离,将 wal 目录与 data 目录分别映射到不同的磁盘上,以减少读写操作的相互影响。
Influx CLI
Bucket
influx bucket create \
--org dx \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
--name newBucket
influx bucket list \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
--org dx
influx bucket delete \
--org dx \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
--name newBucket
Write
influx write --bucket demo \
--org dx \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
'word_count,word=cc word_count=10'
Query
influx query \
--org dx \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
'from(bucket: "demo")
|> range(start: -1d, stop: -0s)
|> filter(fn: (r) => r["_measurement"] == "world_count")
|> drop(columns: ["_start","_stop"])'
FluxQL
from(bucket: "demo")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "word_count")
// |> window(every: 5m)
// |> mean()
// |> duplicate(column: "_stop", as: "_time")
// |> window(every: inf)
|> aggregateWindow(every:5m, fn: mean, createEmpty: false)
|> yield(name: "mean") // |> sort(columns:["_value"])
// |> group(columns:["_value"])
// |> keep(columns:["_value"])
// |> drop(columns: ["_start","_stop"])
// |> limit(n: 3, offset: 2)
// |> yield(name: "mean")
Delete
influx delete --bucket demo \
--org dx \
--token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g== \
--start '1970-01-01T00:00:00Z' \
--stop $(date +"%Y-%m-%dT%H:%M:%SZ") \
--predicate '_measurement="myMeasurement"'
InfluxDB API
通过HTTP的方式对 InfluxDB 进行管理
写入
curl --request POST "http://121.5.133.125:8086/api/v2/write?org=dx&bucket=demo&precision=s" \
--header "Authorization: Token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g==" \
--data-raw "
word_count,word=dd word_count=3i 1624986861
word_count,word=dd word_count=4i 1624987862
word_count,word=dd word_count=6i 1624988863
word_count,word=dd word_count=2i 1624989864
word_count,word=ee word_count=2i 1624990865
word_count,word=ee word_count=4i 1624991866
"
查询
curl --location --request POST 'http://121.5.133.125:8086/api/v2/query?org=dx' \
--header 'Authorization: Token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g==' \
--header 'Accept: application/csv' \
--header 'Content-type: application/vnd.flux' \
--data-raw 'from(bucket: "demo")
|> range(start: -1d, stop: -0s)
|> filter(fn: (r) => r["_measurement"] == "world_count")'
删除
curl --location --request POST 'http://121.5.133.125:8086/api/v2/delete/?org=dx&bucket=demo' \
--header 'Authorization: Token 2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g==' \
--header 'Content-Type: application/json' \
--data-raw '{
"start": "2021-06-29T00:00:00Z",
"stop": "2021-07-01T00:00:00Z",
"predicate": "_measurement=word_count AND word=cc"
}'
Java API
依赖
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>2.3.0</version>
</dependency>
应用
public class BaseInfluxDBTest {
protected static InfluxDBClient influxDBClient;
private static char[] token = "2y7jSDzWL6PfafiQ1g_RrinwaI2SmgvY-Oo2AdBxkS4ULnUrgbvr1G-P05MixgBqt1pINAf3GmOa1mjBfHz39g==".toCharArray();
@BeforeAll
static void open() {
influxDBClient = InfluxDBClientFactory.create("http://121.5.133.125:8086", token, "dx", "demo");
}
@AfterAll
static void close() {
influxDBClient.close();
}
}
Telegraf
Telegraf是一个插件驱动的服务器代理,用于从数据库、系统和物联网传感器收集和发送度量和事件。Telegraf是用Go编写的,可以编译成一个没有外部依赖关系的二进制文件,并且需要非常小的内存占用。
应用:
- 数据库:连接到MongoDB、MySQL、Redis等数据源,收集和发送度量数据。
- 系统:从云平台、容器和编排器的现代堆栈中收集度量。
- 物联网传感器:从物联网传感器和设备收集关键状态数据(压力水平、温度水平等)。