深入理解InfluxDB时序数据库:从原理到实践
目录
一、时序数据库概述
1.1 什么是时序数据库
时序数据库(Time Series Database)是专门为处理时间序列数据优化的数据库系统,主要特点:
- 数据按时间顺序存储
- 高性能写入能力
- 高效的时间范围查询
- 内置数据压缩和过期策略
1.2 典型应用场景
场景类别 | 具体示例 |
---|---|
系统监控 | CPU使用率、内存占用、QPS监控 |
IoT设备 | 传感器数据、设备状态 |
业务指标 | 用户活跃度、交易量统计 |
环境监测 | 温度、湿度、空气质量 |
1.3 为什么选择InfluxDB
✔️ 专为时序数据优化的存储引擎
✔️ 高性能写入(每秒百万级数据点)
✔️ 内置数据保留策略和连续查询
✔️ 完善的监控生态系统集成
✔️ 开源版本功能完备
二、InfluxDB核心概念详解
2.1 数据模型
Measurement (表)
└── Tags (索引字段,字符串类型)
└── Fields (数值字段)
└── Timestamp (时间戳)
2.2 与关系型数据库对比
InfluxDB概念 | MySQL对应 | 说明 |
---|---|---|
Database | Database | 数据库 |
Measurement | Table | 数据表 |
Point | Row | 数据行 |
Series | 索引组合 | Tags+Measurement唯一标识 |
2.3 关键组件解析
Point结构
public class Point {
private String measurement; // 表名
private Map<String,String> tags; // 标签集
private Map<String,Object> fields; // 字段值
private Instant time; // 时间戳
}
point
的概念就类似于mysql
里一行数据
(data point,直译为数据点,可以理解成一行数据),point
的构成部分有三个:
point属性 | 属性解释 | 备注 |
---|---|---|
time | 时间戳 | influxdb自带字段,单位:纳秒 |
tags | 有各种索引的属性 | 可设置多个,逗号隔开 |
fields | 没有索引的属性 | 可设置多个,逗号隔开 |
tags
和fileds
的区别:
1. field无索引,一般表示会随着时间戳而发生改变的属性,比如温度、经纬度等,类型可以是 string, float, int, bool
2. tag有索引,一般表示不会随着时间戳而发生改变的属性,比如城市、编号等,类型只可为 string
比较值得注意的是:在InfluxDB
中,tags
决定了series
的数量,而series
的数量越大,对于系统CPU和InfluxDB
的性能影响越大。
Retention Policy(数据保留策略)
-- 创建保留策略示例
CREATE RETENTION POLICY "one_year"
ON "iot_db"
DURATION 365d
REPLICATION 1
SHARD DURATION 7d
DEFAULT
参数说明:
DURATION
:数据保留时长SHARD DURENTION
:分片时长(影响查询效率)REPLICATION
:副本数(集群版有效)
duration
:保留多长时间的数据,比如3w,意思就是把三周前的数据清除掉,只保留三周内的,支持h(小时),d(天),w(星期)这几种配法。
shardGroupDuration
:存储数据的分组结构,比如设置为1d,表示的是每组存储1d的数据量,也就是说数据将按照天为单位划分存储分组,然后根据每条数据的时间戳决定把它放到哪个分组里,因此这个概念还会影响到过期策略,因为InfluxDB在清除过期数据时不可能逐条清理,而是通过清除整个ShardGroup
的方式进行,因为通过跟当前时间对比就可以知道哪些分组里的数据一定是过期的,从而进行整组清理,效率往往更高。
replicaN
:副本数量,一般为1个(这个大概就是备份的意思?这个配置在单实例模式下不起作用)
default
:是否是默认配置,设置为true
表示默认的意思,主要用于查询时是否指定策略,如果不指定,则这个查询就是针对default=true
的策略进行的,注意,本文章里的sql都没有指明保留策略的名字,如果有需要,那么请指定。一个库允许有多套策略配置(每套策略里都可以有自己的一份数据,比如同样一张表的数据在策略A和策略B的情况下是不同的,可以理解为一个库对应N个策略,每个策略里有自己的N多张表,相互独立),在不指定策略名称的情况下写的sql,默认使用default=true
的策略。
当然,策略也支持修改,指令如下:
alter retention policy "autogen" on "test" duration 30d default
上面这条指令就会把上面策略明为autogen
的策略有效保存时间改为30天
。
解决端口被占用问题
1、查看端口是否被占用:
netstat -anp |grep 8086 //查看8888端口的占用情况
/*出现这种情况是被占用了
tcp 0 0 0.0.0.0:8086 0.0.0.0:* LISTEN 5756/docker-proxy
tcp6 0 0 :::8086 :::* LISTEN 5760/docker-proxy
*/
lsof -i :8086 //查看占用此端口的进程PID
kill -9 4110 //杀死进程
三、InfluxDB语法
1.8版本语法
--安装influxdb
[root@localhost ~]#docker run -itd --name influxdb1.8 -p 8086:8086 influxdb:1.8-alpine
--查看是否在运行,不运行的话执行下两句
[root@localhost ~]#docker ps
[root@localhost ~]#docker start influxdb1.8
--进入influxdb
[root@localhost ~]# docker exec -it influxdb1.8 /bin/bash
--连接influxdb precision参数 rfc3339:YYYY-MM-DDTHH:MM:SS.nnnnnnnnnZ
bash-5.1# influx -precision rfc3339
--当开启ssl认证的时候 host后跟influxdb的ip
bash-5.1# influx -ssl -unsafeSsl -host 10.10.32.235 -precision rfc3339
--出现以下语句证明连接成功
Connected to http://localhost:8086 version 1.8.10
InfluxDB shell version: 1.8.10
--在创建database、measurements以及tag keys、field keys时,尽量避开inflxudb的保留关键字,否则需要在select和insert语法上对保留字加英文双引号。
ALL ALTER ANALYZE ANY AS ASC
BEGIN BY CREATE CONTINUOUS DATABASE DATABASES
DEFAULT DELETE DESC DESTINATIONS DIAGNOSTICS DISTINCT
DROP DURATION END EVERY EXPLAIN FIELD
FOR FROM GRANT GRANTS GROUP GROUPS
IN INF INSERT INTO KEY KEYS
KILL LIMIT SHOW MEASUREMENT MEASUREMENTS NAME
OFFSET ON ORDER PASSWORD POLICY POLICIES
PRIVILEGES QUERIES QUERY READ REPLICATION RESAMPLE
RETENTION REVOKE SELECT SERIES SET SHARD
SHARDS SLIMIT SOFFSET STATS SUBSCRIPTION SUBSCRIPTIONS
TAG TO USER USERS VALUES WHERE
WITH WRITE
--创建库
create database hiot
--删除库
drop database hiot
--查看库
show databases
--使用库
use hiot
/*创建保存策略 default表示默认策略
replication 1:副本个数
rp_name:策略名;
db_name:具体的数据库名;
1w:保存1周,1周之前的数据将被删除,influxdb具有各种事件参数,比如:h(小时),d(天),w(星期),设置为0则是不删除(默认);
*/
create retention policy "rp_name" on "db_name" duration 3w replication 1 default
create retention policy one_week on hiot duration 1w REPLICATION 1 default
CREATE RETENTION POLICY system_metric ON hiot DURATION 90d REPLICATION 1 SHARD DURATION 24h
--删除保存策略
DROP RETENTION POLICY <retention_policy_name> ON <database_name>
drop retention policy one_month on hiot
--展示某个库的rp
show retention policies on hiot
--插入,空格之前是tag,之后是field(必须存在)
--field无索引,一般表示会随着时间戳而发生改变的属性,比如温度、经纬度等,类型可以是 string, float, int, bool
--tag有索引,一般表示不会随着时间戳而发生改变的属性,比如城市、编号等,类型只可为 string
insert aaa,t1=看看,t2=a age=26,id=12,blog="https://www.baidu.com"
--删除
drop measurement aaa
--RP.measurement
select * from one_week.aaa
--FROM <database_name>..<measurement_name>从用户指定的数据库和DEFAULT 保留策略中的测量返回数据。
select age::field,t1::tag from hiot..aaa
--FROM <measurement_name>从单个测量返回数据。
select * from aaa
--常见的查询tag的语法如下
show tag keys on <database> from <measurement>
--查看field key的语句如下
show field keys on <database> from <measurement>
/*查找 查找时必须包含field字段,不能只有tag字段 where后面判断字段如果是tag,必须加单引号
原因:tag key/value 都是字符串类型,而且会建立索引
field key/value类型可以为:浮点,字符串,整型 没有索引
SELECT <field_key>[,<field_key>,<tag_key>] FROM <measurement_name>[,<measurement_name>]
*/
select t1,age,id from aaa where id=12
select age,blog from aaa where t2='a'
--返回特定字段和标签 SELECT "<field_key>"::field,"<tag_key>"::tag
select age::field,t1::tag from aaa
--从多个measurement查询
select * from aaa,bbb
--返回具有过去 1小时内时间戳的数据
select * from "aaa" where time>now()-1h
--不能用于GROUP BY对field进行分组。 mean()均值
select mean("age") from aaa group by t2
--GROUP BY time() 每个时间戳的结果 代表一个 1小时的间隔。 需要有函数和时间范围 每个小时范围为前闭后开
select sum(age) from aaa where time>='2022-08-18T00:00:00Z' and time <='2022-08-19T14:00:00Z' group by time(1h) order by time desc limit 20
--高级语法 时间偏移 -号代表时间向后 ,不写代表时间向前
select sum(age) from aaa where time>='2022-08-18T00:00:00Z' and time <='2022-08-19T14:00:00Z' group by time(1h,30m) order by time desc limit 20
--fill()更改为没有数据的时间间隔报告的值。
/*linear 报告没有数据的时间间隔的线性插值结果。
none 报告没有时间戳和没有数据的时间间隔的值,空的结果不显示。
null 对于没有数据但返回时间戳的时间间隔报告 null。这与默认行为相同。
previous 报告没有数据的时间间隔的前一个时间间隔的值。
*/
select sum(age) from aaa where time>='2022-08-18T00:00:00Z' and time <='2022-08-19T14:00:00Z' group by time(1h,30m) fill(1000) order by time desc limit 20
/*复制表
SELECT *
INTO <destination_database>.<retention_policy_name>.<measurement_name>
FROM <source_database>.<retention_policy_name>.<measurement_name> group by *
*/
select * into hiots..newaaa from hiot..aaa group by *
2.3版本语法
--安装
/*-e DOCKER_INFLUXDB_INIT_MODE=setup 指其定为初始化模式。
-e DOCKER_INFLUXDB_INIT_USERNAME=alming 指定用户名
-e DOCKER_INFLUXDB_INIT_PASSWORD=wuxianming 指定密码
-e DOCKER_INFLUXDB_INIT_ORG=alming 创建初始org
-e DOCKER_INFLUXDB_INIT_BUCKET=alming 创建初始bucket
*/
docker run -d -p 10001:8086 --name influxdb2.3\
-e DOCKER_INFLUXDB_INIT_MODE=setup \
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
-e DOCKER_INFLUXDB_INIT_PASSWORD=123456 \
-e DOCKER_INFLUXDB_INIT_ORG=hvision \
-e DOCKER_INFLUXDB_INIT_BUCKET=h-bucket \
influxdb:2.3.0
--定义数据源
from(bucket:"example-bucket")
--添加数据,会自动创建measurement
aaa,t1=123,t2=a age=26,id=12,blog="https://www.baidu.com"
--开始结束时间
from(bucket:"example-bucket")
|> range(start: -1h, stop: -10m)
--过滤 必须是== 字段名可以._field 标签名直接.标签名称
|> filter(fn: (r) => r._measurement == "aaa" and r._field == "age" and r._value == 25 and r.t1 == "123")
--以五分钟为间隔的窗口数据 window(every: 5m)
--yield()输出查询结果。只有在同一个 Flux 查询中包含多个查询时,才需要显式调用。
--sort按降序对结果进行排序。默认为false 只能对time和tag排序 sort(columns: ["_time"], desc: true)
--管道转发运算符 ( |>) 将每个函数的输出作为输入发送到下一个函数
--limit(n: 3, offset: 2)将结果限制为每个输入表中前两行之后的前三行 limit限制的每个字段的表,必须要过滤完所有字段才能达到想要的结果
--group只能对tag或者time分组
--用于map() 将分子操作数值除以分母操作数值并乘以 100。
--向influxDB中存入整形数据,在数据后加上i就可以了
--pivot()将操作数对齐到行中
pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
--fill(usePrevious: true)上一个非空值 fill(value: 0.0) 填充指定值
--用first()或 last()返回输入表中的第一条或最后一条记录
/*
=~:当左边的表达式匹配右边的正则表达式时,它的计算结果为true。
!~:当左边的表达式与右边的正则表达式不匹配时,计算结果为true。
*/
/*用于group()取消分组数据并在单个表中返回结果。
使用keep()和unique()返回指定列中的唯一值。*/
from(bucket: "h-bucket")
|> range(start: -1d)
|> filter(fn: (r) => r["_measurement"] == "test")
|> filter(fn: (r) => r["_field"] == "money")
|> group()
|> keep(columns: ["id"])
|> unique(column: "id")
--重新计算 _values 列 使用with运算符 in map()覆盖现有_value列 若是新列 修改_value为自定义列名
from(bucket: "noaa")
|> filter(fn: (r) => r._measurement == "average_temperature")
|> range(start: -30d)
|> map(fn: (r) => ({r with _value: (float(v: r._value) - 32.0) * 5.0 / 9.0} ))
--计算值的总值
data=from(bucket: "h-bucket")
|> range(start: -1d)
|> filter(fn: (r) => r["_measurement"] == "test")
|> filter(fn: (r) => r["_field"] == "money")
|> group(columns: ["money"])
|> keep(columns: ["_value"])
|> unique(column: "_value")
data
|> cumulativeSum()
--aggregateWindow() 将数据分割成时间窗口
data=from(bucket: "h-bucket")
|> range(start: -1d)
|> filter(fn: (r) => r["_measurement"] == "test")
|> filter(fn: (r) => r["_field"] == "money")
|> group(columns: ["id"])
|> keep(columns: ["_value","_time"])
|> unique(column: "_value")
data
|> aggregateWindow(every: 5m, fn: sum)
|> cumulativeSum()
--查找tag标签的值
import "influxdata/influxdb/schema"
schema.measurementTagValues(
bucket: "demo-bucket",
measurement: "test",
tag: "id",
)
--列出当前组织中的所有存储桶。
buckets()
|> rename(columns: {"name": "_value"})
|> keep(columns: ["_value"])
--列出桶中所有measurement
import "influxdata/influxdb/schema"
schema.measurements(bucket: "bucket-name")
--列出measurement中所有字段
import "influxdata/influxdb/schema"
schema.measurementTagValues(
bucket: "bucket-name",
measurement: "measurement-name",
tag: "_field",
)
--列出唯一标签值
import "influxdata/influxdb/schema"
schema.tagValues(bucket: "h-bucket", tag: "id")
/*
定义一个名为“cq-mem-data-1w”的任务,每周运行一次(可在界面上直接创建)。
定义一个data变量,该变量表示 存储桶mem测量中最近 2 周的所有数据
使用该aggregateWindow()函数 将数据窗口化为 1 小时间隔并计算每个间隔的平均值。
将聚合数据存储在组织system-data-downsampled下的存储桶中 my-org
to() 将转换后的数据写回另一个 InfluxDB 存储桶:
*/
option task = {name: "cq-mem-data-1w", every: 1w}
data = from(bucket: "system-data")
|> range(start: -duration(v: int(v: task.every) * 2))
|> filter(fn: (r) => r._measurement == "mem")
data
|> aggregateWindow(fn: mean, every: 1h)
|> to(bucket: "system-data-downsampled", org: "my-org")
--实例
from(bucket: "lqp-bucket")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "aaa" and r._field == "age")
|> window(every: 3m)
from(bucket: "h-bucket")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "equipment")
|> filter(fn: (r) => r["_field"] == "age")
|> group(columns: ["age"])
|> sort(columns: ["time"], desc: true)
|> limit(n: 1)
bucket:所有 InfluxDB 数据都存储在一个存储桶中。一个桶结合了数据库的概念和存储周期(时间每个数据点仍然存在持续时间)。一个桶属于一个组织
bucket schema:具有明确的schema-type的存储桶需要为每个度量指定显式架构。测量包含标签、字段和时间戳。显式模式限制了可以写入该度量的数据的形状。
organization:InfluxDB组织是一组用户的工作区。所有仪表板、任务、存储桶和用户都属于一个组织。
bucket的数据存储时间并不能随意更换,可以将永久存储的数据改成Retention存储,但是也存在限制;无法将有Retention的数据改成永久存储。
四、SpringBoot集成influxdb
SpringBoot操作influxdb2
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>flux-dsl</artifactId>
<version>6.0.0</version>
</dependency>
//flux读取数据
@Test
public void test03(){
Restrictions restrictions = Restrictions.and(Restrictions.measurement().equal("new"));
Restrictions age = Restrictions.field().equal("age");
Flux flux = Flux.from("h-bucket").
range().withStart("-1h").withStop("-1m").
filter(restrictions).filter(age).sort(new String[]{"id"},true);
InfluxDBClient client = influxConfig.influxDBClient();
List<FluxTable> query = client.getQueryApi().query(flux.toString());
for (FluxTable fluxTable : query) {
List<FluxRecord> records = fluxTable.getRecords();
for (FluxRecord record : records) {
System.out.println(record.getTime()+":"+record.getField()+":"+record.getValue()+":"+record.getValueByKey("id"));
}
}
client.close();
}
//拼接读取
@Test
public void test01(){
InfluxDBClient client = influxConfig.influxDBClient();
WriteApi writeApi = client.makeWriteApi();
String val = "equipment";
writeApi.writeRecord(WritePrecision.NS,val);
String queryVal="from(bucket: \"h-bucket\")\n" +
" |> range(start: -5h, stop: -1m)\n" +
" |> filter(fn: (r) => r[\"_measurement\"] == \"equipment\")\n" +
" |> filter(fn: (r) => r[\"_field\"] == \"age\")";
List<FluxTable> tables = client.getQueryApi().query(queryVal);
for (FluxTable fluxTable : tables) {
List<FluxRecord> records = fluxTable.getRecords();
for (FluxRecord record : records) {
System.out.println(record.getTime()+":"+record.getField()+record.getValue()+":"+record.getValueByKey("_value"));
}
}
}
//写数据
public void test02(){
InfluxDBClient client = influxConfig.influxDBClient();
WriteApi writeApi = client.makeWriteApi();
Point point = Point.measurement("test")
.addTag("id", "11")
.addField("money", 1100);
writeApi.writePoint(point);
client.close();
}
第一种配置
#配置文件
influx:
url: http://192.168.10.12:10001
token: lNCZy6_cqLD83idLA4Wb5uB02zqnKKX1aMkVim0IHdNTGY5OPxSOHRYozl7h5qj34TwyvKr3fhkB_TUAe_TV0Q==
bucket: demo-bucket
org: h-demo
@Configuration
@ConfigurationProperties(prefix = "influx")
public class InfluxConfig {
private String url;
private String token;
private String org;
private String bucket;
@Bean
public InfluxDBClient influxDBClient(){
return InfluxDBClientFactory.create(url, token.toCharArray(),org,bucket);
}
public void setUrl(String url) {
this.url = url;
}
public void setToken(String token) {
this.token = token;
}
public void setOrg(String org) {
this.org = org;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
}
第二种配置:
创建名为influx2.properties
的配置文件
influx2.url=http://192.168.10.12:10001
influx2.token=lNCZy6_cqLD83idLA4Wb5uB02zqnKKX1aMkVim0IHdNTGY5OPxSOHRYozl7h5qj34TwyvKr3fhkB_TUAe_TV0Q==
influx2.bucket=demo-bucket
influx2.org=h-demo
@Configuration
public class InfluxConfigProperties {
@Bean
public InfluxDBClient influxDBClient() {
return InfluxDBClientFactory.create();
}
}