InfluxDB是一个开源的时序数据库,也是目前Github上最流行、性能最好的开源时序数据库之一。它由Go语言编写,用于存储和查询数据。相比于OpenTSDB,它部署相对简单,不需要额外的外部依赖。当前,influxDB分为1.x和2.x版本,而2.x版本又分为开源版本和企业版本。1.x版本是传统的开源版本,目前已经更新到了1.8.3;2.x开源版本当前还停留在rc(最终测试)版本。两者之间差距较大,本文专注于1.x版本。
1. 名词介绍
timestamp:时间戳,RFC3339格式
filed key/value:相当于一条记录里的值,例如butterflies = 3,一条记录里可以有多个
tag key/value:相当于一条记录里的属性,例如scientist = langstroth,一条记录里可以有多个,会被全索引
field set:filed key/value的集合
tag set:tag key/value的集合
measurement:概念类似传统数据库的表,含有timestamp、filed和tag列
retention police:保留策略,默认为数据永不删除且只有一份数据
series:具有共同retention policy、measurement和tag set的集合
point:唯一存在的一条记录,即timestamp和series的组合唯一存在的field
2. InfluxQL
……
SELECT 语句
select * from “
查询measurement下的所有数据。
select count(“
查询measurement下data非空的数据量。
3. 存储
参考:In-memory indexing and the Time-Structured Merge Tree (TSM)
作为一个热门的时序数据库,influxDB的存储也有其独到之处,它主要有以下组件构成:
内存索引(In-Memory Index):内存中的索引是所有shards公用的,可以快速检索measurements、tags和series。
WAL(Write Ahead Log):这是一种针对写优化的存储格式,可持久写入,但查询不易。其文件形式类似于_000001.wal,文件号单增,默认达到10MB后开启一个新文件。其存储写入和删除操作的压缩块。在influxDB重启后,会重新读取wal文件创建内存缓存。
缓存(Cache):WAL存储数据在内存中的副本,在内存中时不压缩,最终存储到TSM中。WAL和缓存是在客户端数据写入时同时写入的,其中WAL主要用于当influxDB宕掉重启后恢复内存中还没来得及持久化到TSM中的数据,即Cache数据;而Cache数据在数据量达到阈值后合并持久化到TSM存储文件中。在查询时,会将内存中的数据和TSM中的数据合并并生成新的副本,因此查询是在内存数据的副本中进行的,查询过程中的写入不会影响查询结果。可以设置缓存阈值,参见本小节参考。
TSM文件:压缩存储数据。
FileStore:主导对磁盘上所有TSM文件的访问。在influxDB重启后自动导入所有TSM文件,并删除不再使用的TSM文件。
压缩器(Compactor):检测合并机制组件,在后台持续运行,默认每隔1s检查一次,负责将Cache和TSM文件转化为读优化的格式,主要通过压缩series、删除已删除的数据、优化索引和合并小文件。
Compaction Planner:确定哪些TSM文件已经准备好进行压缩,并确保多个并发的压缩不会相互干扰。
Compression:
Writers/Readers:每种文件类型都有各自的Writers/Readers。
存储目录
influxDB的数据存储目录默认情况下有meta、wal和data三个目录,其中meta存储数据库的一些元数据,目录下有一个meta.db文件;wal目录下存放wal文件;data目录下存放tsm文件。
TSM文件
TSM文件是内存映射的只读文件集合,由4部分组成:header、blocks、index和footer。具体结构如下:
1 | +--------+------------------------------------+-------------+--------------+ |
2 | | Header | Blocks | Index | Footer | |
3 | |5 bytes | N bytes | N bytes | 4 bytes | |
4 | +--------+------------------------------------+-------------+--------------+ |
Header中,magic数字用于区分文件类型,version用于区分版本号。
1 | +-------------------+ |
2 | | Header | |
3 | +-------------------+ |
4 | | Magic │ Version | |
5 | | 4 bytes │ 1 byte | |
6 | +-------------------+ |
块
1 | +--------------------------------------------------------------------+ |
2 | │ Blocks │ |
3 | +---------------------+-----------------------+----------------------+ |
4 | | Block 1 | Block 2 | Block N | |
5 | +---------------------+-----------------------+----------------------+ |
6 | | CRC | Data | CRC | Data | CRC | Data | |
7 | | 4 bytes | N bytes | 4 bytes | N bytes | 4 bytes | N bytes | |
8 | +---------------------+-----------------------+----------------------+ |
索引
1 | +-----------------------------------------------------------------------------+ |
2 | │ Index │ |
3 | +-----------------------------------------------------------------------------+ |
4 | │ Key Len │ Key │ Type │ Count │Min Time │Max Time │ Offset │ Size │...│ |
5 | │ 2 bytes │ N bytes │1 byte│2 bytes│ 8 bytes │ 8 bytes │8 bytes │4 bytes │ │ |
6 | +-----------------------------------------------------------------------------+ |
footer存储index的起点偏移量。
1 | +---------+ |
2 | │ Footer │ |
3 | +---------+ |
4 | │Index Ofs│ |
5 | │ 8 bytes │ |
6 | +---------+ |
4. 内存占用
当前,influxDB同样存在许多问题,首当其冲的便是内存占用问题。随着influxDB使用时间/数据量的增长,influxDB的内存占用会成GB地增长,占用大量的资源。
倒排索引
influxDB内存占用最主要的因素在于其使用内存构建索引,因此随着索引量的增加,其内存占用也会不断上涨。InfluxDB的索引默认是基于内存的倒排索引,由两个map组成,分别是map<SeriesID, SeriesKey>
和map<tagKey, map<tagValue, List<SeriesID\>>>
。一个查询过程如下:
- 通过第二个map确定符合查询语句各个tag条件的SeriesID的集合;
- 各个SeriesID的集合做交集形成完全符合查询语句tag条件的SeriesID的集合;
- 通过第一个map找到集合中SeriesID对应SeriesKey的集合;
- 根据时间范围找到所有满足条件的时序数据集合。
通过这种基于内存的索引方案会使得查询十分高效,但显然在许多情况下(例如集群监控),serieskey的数量会非常多,导致内存消耗过大;另外,如果influxDB意外宕掉,需要扫描全部的TSM文件再再内存中全量重构索引,恢复时间较长。因此,influxDB推出了基于磁盘存储的索引TSI。
TSI方案将索引持久化到磁盘,并在使用时再加载到内存。