InfluxDB(一)初探时序数据库
最近公司有个需求需要借助
InfluxDB
实现(或者更准确的说,使用该数据库可以更容易的实现),因此稍微看了下这个数据库,把比较重要的一些东西先简单记录一下,日后如果踩坑,也会继续在下面补充。
零、下载&安装
官方地址:https://portal.influxdata.com/downloads/
一、什么是时序数据库,它可以用来做什么?
简单来说时序数据库
就是存储带有时间戳
且包含随时间发生变化
的数据,InfluxDB
属于一种时序数据库
。这类数据具体指什么数据呢?这里举几个例子:
- 监控,比如某一时段CPU占用率、服务QPS、服务耗时等监控数据
- 某飞机在运行过程中各时刻的高度、速度、机舱内温度、湿度
- 某地区每时每刻的室外温度记录
上述的数据都满足某一个具体的事物(监控指标、飞机、地区)随着时间的变化而需要记录的一些数据(监控数据、速度、温度等),这些数据被称为时序性数据,这些数据可以被用来做大数据分析、人工智能等。
Q:通过上述的例子,这些无非是一些指标数据嘛,为什么非得用时序性数据库记录?不可以用
Mysql
这种关系型数据库来做吗?A:不可以,首先对于这种数据,量级上是巨大的,比如每时每刻都要记录某飞机的坐标变化,类似这种指标,量级很庞大,关系型数据库设计之初并不是为了处理这种大数据量级的,即便是后来的分表分库,也是为了优化数据量和访问量大了以后查询和写入性能问题的,所以不适合用来存储数量如此庞大的数据,所以这并不是能不能实现的问题,而是适不适合的问题,而时序型数据库的写入速度非常快,其次内部还会对存储数据进行压缩,同时提供了更高的可用性,比如数据保留策略、数据聚合函数、连续查询等。数据保留策略可以保证超过指定期限的数据被回收,释放磁盘空间。
二、InfluxDB基本概念
2.1:建表
不需要专门建表,一般insert
执行过去时,如果发现表不存在,就自动创建。
2.2:基本概念与mysql中概念的对应关系
概念名称 | 概念解释 | MySQL类比 |
---|---|---|
database | 数据库 | 类似mysql里的库 |
measurement | 表 | 类似mysql里的表 |
point | 一行数据记录 | 类似mysql里的一行数据 |
2.3:point详解
point
的概念就类似于mysql
里一行数据
(data point,直译为数据点,可以理解成一行数据),point
的构成部分有三个:
point属性 | 属性解释 | 备注 |
---|---|---|
time | 时间戳 | influxdb自带字段,单位:纳秒 |
tags | 有各种索引的属性 | 可设置多个,逗号隔开 |
fields | 没有索引的属性 | 可设置多个,逗号隔开 |
tags
和fileds
的区别:
1 | 1. field无索引,一般表示会随着时间戳而发生改变的属性,比如温度、经纬度等,类型可以是 string, float, int, bool |
比较值得注意的是:在InfluxDB
中,tags
决定了series
的数量,而series
的数量越大,对于系统CPU和InfluxDB
的性能影响越大,series
估算方法下面会说。
2.4:sql语句示范
建库:
1 | create database "库名" |
一般来说,新建完库,还要对该库设置自己的时效策略
(RP
),下面会详细说明RP
的设置方法,一般建好库后都会有一个默认的RP策略
插入数据:
1 | insert results,hostname=index2,name=index2333 value=2,value2=4 |
上面这句sql的意思就是说在名为result
的表里,插入两个分别命名为hostname
和name
取值分别为index2
和index2333
的tags
,以及两个分别命名为value
和value2
取值分别为2和4的fields
的数据。
⚠️ 注意:这里的指令直接输入整数,
InfluxDB
是按照浮点型
处理的,如果一定要让上面的value=2和value2=4中的2和4是整型数据,那么需要在后面加上修饰词:value=2i,i就代表整型。
检索数据:
1 | select * from results |
打印结果为:
1 | name: results2 |
分页检索:
1 | SELECT * FROM 表 WHERE 条件 LIMIT rows OFFSET (page - 1)*rows |
rows代表每页展示行数,page表示页码
删除表:
1 | drop measurement "表名" |
三、InfluxDB的时效设置
很遗憾,InfluxDB
是不支持删除和修改的,删除有专门的操作,但是性能很低,不建议使用。
InfluxDB
支持定期清除数据策略。
查看当前库对应策略配置的命令为:
1 | show retention policies on "库名" |
结果:
1 | name duration shardGroupDuration replicaN default |
name
:过期策略名
duration
:保留多长时间的数据,比如3w,意思就是把三周前的数据清除掉,只保留三周内的,支持h(小时),d(天),w(星期)这几种配法。
shardGroupDuration
:存储数据的分组结构,比如设置为1d,表示的是每组存储1d的数据量,也就是说数据将按照天为单位划分存储分组,然后根据每条数据的时间戳决定把它放到哪个分组里,因此这个概念还会影响到过期策略,因为InfluxDB在清除过期数据时不可能逐条清理,而是通过清除整个ShardGroup
的方式进行,因为通过跟当前时间对比就可以知道哪些分组里的数据一定是过期的,从而进行整组清理,效率往往更高。
replicaN
:副本数量,一般为1个(这个大概就是备份的意思?这个配置在单实例模式下不起作用)
default
:是否是默认配置,设置为true
表示默认的意思,主要用于查询时是否指定策略,如果不指定,则这个查询就是针对default=true
的策略进行的,注意,本文章里的sql都没有指明保留策略的名字,如果有需要,那么请指定。一个库允许有多套策略配置(每套策略里都可以有自己的一份数据,比如同样一张表的数据在策略A和策略B的情况下是不同的,可以理解为一个库对应N个策略,每个策略里有自己的N多张表,相互独立),在不指定策略名称的情况下写的sql,默认使用default=true
的策略。
当然,策略也支持修改,指令如下:
1 | alter retention policy "autogen" on "test" duration 30d default |
上面这条指令就会把上面策略明为autogen
的策略有效保存时间改为30天
。
四、InfluxDB的存储结构
4.1:结构层级
了解完策略,结合上面提到的series
、tags
、fields
等概念,画一下单个InfluxDB库
的存储结构:
图里没有体现出tags
、fields
这些数据层面的东西,它们最终被存放在了Shard
里,之前说过tags
会影响series
的大小,其实series
就相当于一个唯一化分类,eries
估算方式为:
1 | series的个数 = RP × measurement × tags(tags去重后的个数) |
比如一个database
中有一个measurement
,叫test
,有两个RP
(7d, 30d),tag
有host
,server
。host的值有hostA、hostB
,server为server1,server2
。那么这个database的series值为2RP x 4tags = 8
4.2:LSM-Tree
回归图1
前,先来了解下LSM-Tree
,几乎所有的k-v存储的写密集型数据库都采用该数据结构实现,该数据结构的结构如下(注意下面说的层级并非树的层级,而是合并逻辑发生时的层级):
如图2
,该结构写入流程如下:
写入的数据首先加到0层,0层的数据存储在内存中,短期内查询的数据一般在0层,由于是内存操作,因此效率会非常高,当0层的数据达到一定大小时,此时会把0层 和位于它下面的1层进行合并,然后合并出来的新的数据(0层+1层的数据)会顺序写磁盘(这里由于是顺序写入磁盘,因此写性能会非常好),然后替换掉原来老的1层数据,当1层达到一定大小的时候,将继续和它的下层合并,以此类推,一级一级的往下递,除此之外还可以将合并之后旧文件全部删掉,留下最新的。
LSM-Tree
参考文章:LSM-tree 基本原理及应用
然后回归图1
,最终的数据会被存储进Shard
,Shard
里存在几个区域,就是最终存放数据的地方,下面针对这几个区域说明下它们的作用:
4.3:Cache
相当于上面说的LSM-Tree
的0层
,存放于内存
中,写入的数据时首先被该模块接收并存储,该模块在内存中表现为一个map结构,k = seriesKey + 分隔符 + fieldsName,v = 具体的filed对应的值的数组,具体结构体如下(参考网上资料):
1 | type Cache struct { |
基于前面说的LSM-Tree
,可以知道这里的cache不是持续增长的,而是达到一定值就会进行跟下层存储在磁盘上的数据(1~n层)进行合并,然后清空cache,在InfluxDB中,这一部分存储在磁盘上的数据,就是指TSM File模块
,下面会介绍。
4.4:WAL
在上面对于Cache的描述中,Cache是基于内存做的写入数据接收方,那么如果中途机器宕掉,那么就会造成Cache里数据丢失的问题,为了解决这个问题,InfluxDB就设计了WAL模块
,实际在写入一个数据时,不仅会先写进Cache,还会写入WAL
,可以简单理解WAL就是对Cache里数据的备份
,防止数据丢失,在Cache做完一次合并清除掉自身时,旧的WAL文件也会随之删除,然后新建一个WAL,迎接新一轮的写入。同样的,在InfluxDB启动时,也会先去读取WAL文件初始化Cache模块
。
4.5:TSM File
用于组成TSM-Tree
结构的主要磁盘文件(可以对应图2
的1~n层),内部做了很多存储以及压缩优化,单个TSM File
的最大大小为2GB
。
4.6:Compactor
在后台持续运行的一个task(频率为1s),主要做以下事情:
- 在Cache达到阈值时,进行快照,然后将数据合并并保存在TSM File中
- 合并
TSM File
,将多个小型TSM File进行合并,使得每个文件的大小尽可能达到单个文件最大大小(也就是上面说到的2GB) - 检查并
删除
一些已关闭
的WAL文件
五、具体案例,以及实际的存储目录
结合图1
的结构以及图2
的数据结构,再加上对shard
内部各组件的介绍,下面通过一个实际的例子来探索下InfluxDB实际的存储目录。
现在创建出来一个叫style_rank
的库,且设置默认RP为7d
,保存周期为7天
,默认分组策略为24h
分一次,如图:
图中名为7d
的rp策略
被设置为默认策略,其中duration
被设置为168h
,shardGroupDuration
被设置为了24h
,意味着该策略里的表数据将以7天为一个周期过期,期间以1天为单位分组。
5.1:InfluxDB配置文件
下载下来一个InfluxDB后,通过配置文件influxdb.conf
可以配置各个文件存放的目录:
这几个配置决定了你将产生的数据存放在哪里,建议自定义这个,默认的目录很迷=_=
5.2:具体的数据目录
配置完上面的文件,启动,然后录入数据,然后再去具体的目录下查找具体的数据文件:
元数据层:用来存放当前库的属性
,比如RP
、默认RP
、分组策略
等。
wal层:用来存放刚写入数据的信息,会先写入内存,然后异步写入wal文件
,wal文件用来重启InfluxDB时恢复内存数据用,当wal文件达到一定大小时,会压入data层
,wal层
的结构和data层
的几乎一致
:
上面进入wal目录后,需要选择正确的库,这里选择style_rank
,进入库后,会显示出该库的所有RP,这里选择7d
,然后对应图1
,你现在来到了ShardGrou
p层,图6
中的编号,就是生成的ShardGroup
,随便进入一个,则可以看到最终的wal文件
(事实上只有当天建的group内的wal才有数据)。
data层:用来最终存放数据的目录,所有wal文件
内的数据达到一定大小后,均会被压缩进data层
,现在进入data层
,然后进入style_rank
:
可以看到下方除了有两个RP策略
外,还有_series目录
,这个目录就是存放索引
(tags
字段所产生的索引数据)的地方,用于服务重启时恢复索引数据用。
进入7d
,可以看到跟wal层差不多的ShardGroup
分层,你现在又来到了图1
的ShardGroup
层:
然后再次进入一个分组内:
最终的tsm文件
就存在该目录下,fields.idx
存放的是表里的fields元数据
,用于写入数据时做效验
用。
六、执行计划
然后继续由上面的style_rank
库,在7d
这个RP
下,新建两张表:
1 | insert season_views,date="201907" season_id=666,views=56789 |
目的很简单,只是为了记录下season_id=xxx
的数据在某一时刻对应的views值
,俩表的区别在于,season_view
仅仅以日期
为tag(作为索引字段,可以忽略不计),season_views_v2
则以season_id
作为tag,某一时刻,两张表的数据量均达到了5kw
且数据一致
,下面,让我们以最简单的方式,查询下这两张表:
ℹ️ ps:
sql
前加explain
关键字可以查看其执行计划
,加explain analyze
可以看到具体每一步执行的耗时。
同样的查询,season_id
加了索引和不加索引的区别:
NUMBER OF SERIES
(扫描系列数,加了索引后根据series的哈希算法,同一个season_id都被分在了同一个Shard里,这里之所以为8,是因为ShardGroup不同)由不加索引的64个,减少到了8个
NUMBER OF FILES
(扫描文件数)由不加索引的54个减少到11个
NUMBER OF BLOCKS
(扫描的数据块)由不加索引的152058减少到了184
七、总结
- 在使用时,尽可能按照某
具体时间段
进行过滤,如果过滤条件筛选出的数据量过大,则会严重影响查询效率。 - 适度使用函数,若使用,请保证筛选条件筛选出的数据量,如果过大,效率会极低,比如在
season_views_v2
中启用TOP函数
,查出前三名,查询耗时超过10s
,加上season_id
条件后,则迅速返回。 - 加索引时需要注意,尽量避免
大数据量的属性做tag
,否则会产生大量series
,生成大量索引数据
,占用过多内存,比如用户级别
的tag(这里的用户级别
是指大型网站用户数量级,一般1亿以上的情况)。 免费版
的InfluxDB不支持集群
,主从需要借助influx-proxy
实现,也可以自己通过监听wal文件
写入,通过消息队列
的方式实现主从同步,wal
类似mysql里的binlog
。- 建议使用的时候,先把自己的库建好,然后再把RP建好,如果嫌麻烦,可以直接把自己新建的RP定义成默认RP,如果RP设置为默认,只会对查询产生影响(当然写的时候也需要指定),在sql中不指定RP的情况下执行,则认为就是走的默认RP。
- 分库,建议按照日期分。