深入理解InfluxDB时序数据库:从原理到实践

深入理解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对应说明
DatabaseDatabase数据库
MeasurementTable数据表
PointRow数据行
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没有索引的属性可设置多个,逗号隔开

tagsfileds的区别:

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();
    }
}

学习资源推荐

  1. 官方文档:https://docs.influxdata.com/
  2. 性能白皮书:InfluxDB Benchmark
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值