InfluxDB简介

时序数据库官网:https://docs.influxdata.com/

官方文档v1.7:https://docs.influxdata.com/influxdb/v1.7/

官方文档v2.0:https://docs.influxdata.com/influxdb/v2.0/get-started/

时序数据库:http://hbasefly.com/category/%e6%97%b6%e5%ba%8f%e6%95%b0%e6%8d%ae%e5%ba%93/?wypgtk=ujslu

以下部分摘自 知乎 mercury的算法铺子 https://zhuanlan.zhihu.com/p/97247465

InfluxDB 是什么

InfluxDB 是用Go语言编写的一个开源分布式时序、事件和指标数据库,无需外部依赖。

InfluxDB在DB-Engines的时序数据库类别里排名第一。

APM diagram

为什么要使用InfluxDB

快速部署

InfluxDB以单个二进制文件提供了时间序列平台所需的一切-多租户时间序列数据库,UI和仪表板工具,后台处理和监视代理。所有这些使部署和设置变得轻而易举,并且更易于保护。

深度洞察和分析

Flux是第四代编程语言,旨在用于数据脚本,ETL,监视和警报。作为一种功能语言,您可以构建查询并将通用逻辑分离为易于共享并有助于加快开发速度的函数和库。Flux还可以用于与其他SQL数据存储库(Postgres,Microsoft SQL Server,SQLite和SAP Hana)以及基于云的数据存储库(Google Bigtable,Amazon Athena和Snowflake)一起充实您的时间序列数据。丰富的时间序列数据提供了可以进一步深入了解您的数据的上下文。

优化开发人员的生产力

现在,可以在统一的API中访问InfluxDB中的所有内容(提取,查询,存储和可视化)。因为现在可以通过编程方式访问和控制平台中的所有内容,所以这使开发人员能够更快地获得出色的表现。这与跨10种语言(例如Go,Java,PHP和Python)的一组强大的客户端库结合在一起,并且一组InfluxDB命令行工具可帮助开发人员以他们最熟悉的方式进行开发。

从UI开始

InfluxDB具有一流的UI,其中包括Data Explorer,仪表板工具和脚本编辑器。使用数据资源管理器快速浏览您收集的指标和事件数据,并应用常见的转换。仪表板工具随附了方便的可视化列表,可帮助您更快地查看数据见解。最后,使用脚本编辑器通过易于访问的示例,自动完成和实时语法检查来快速学习Flux。

易于构建,易于共享的模板

InfluxDB模板(一套新工具,其中包括打包程序和一套预制的监控解决方案)使您可以与全世界的同事和其他社区成员共享监控专业知识。InfluxDB模板库提供的可用模板涵盖了一些最流行的工具,应用程序和协议。这些模板也可以作为代码检入,以适合您的持续集成和部署管道,从而使部署(更重要的是回滚)更改变得轻松自如。

重要特性

  • 极简架构:单机版的InfluxDB只需要安装一个binary,即可运行使用,完全没有任何的外部依赖。
  • 极强的写入能力: 底层采用自研的TSM存储引擎,TSM也是基于LSM的思想,提供极强的写能力以及高压缩率。
  • 高效查询:对Tags会进行索引,提供高效的检索。
  • InfluxQL:提供SQL-Like的查询语言,极大的方便了使用,数据库在易用性上演进的终极目标都是提供Query Language。
  • Continuous Queries: 通过CQ能够支持auto-rollup和pre-aggregation,对常见的查询操作可以通过CQ来预计算加速查询。

存储引擎: 从LSM 到 TSM

InfluxDB 采用自研的TSM (Time-Structured Merge Tree) 作为存储引擎, 其核心思想是通过牺牲掉一些功能来对性能达到极致优化,其官方文档上有项目存储引擎经历了从LevelDB到BlotDB,再到选择自研TSM的过程,整个选择转变的思考。

时序数据库的需求

  • 数十亿个单独的数据点
  • 高写入吞吐量
  • 高读取吞吐量
  • 大型删除(数据过期)
  • 主要是插入/追加工作负载,很少更新

LSM 的局限性

在官方文档上有写, 为了解决高写入吞吐量的问题, Influxdb 一开始选择了LevelDB 作为其存储引擎。 然而,随着更多地了解人们对时间序列数据的需求,influxdb遇到了一些无法克服的挑战。

LSM (日志结构合并树)为 LevelDB的引擎原理, 具体细节可以参考。 LSM 树原理详解

  1. levelDB 不支持热备份。 对数据库进行安全备份必须关闭后才能复制。LevelDB的RocksDB和HyperLevelDB变体可以解决此问题。
  2. 时序数据库需要提供一种自动管理数据保存的方式。 即删除过期数据, 而在levelDB 中,删除的代价过高。(通过添加墓碑的方式, 段结构合并的时候才会真正物理性的删除)。

InfluxDB 的解决方案 - TSM

按不同的时间范围划分为不同的分区(Shard),因为时序数据写入都是按时间线性产生的,所以分区的产生也是按时间线性增长的,写入通常是在最新的分区,而不会散列到多个分区。分区的优点是数据回收的物理删除非常简单,直接把整个分区删除即可。

  • 在最开始的时候, influxdb 采用的方案每个shard都是一个独立的数据库实例,底层都是一套独立的LevelDB存储引擎。 这时带来的问题是,LevelDB底层采用level compaction策略,每个存储引擎都会打开比较多的文件,随着shard的增多,最终进程打开的文件句柄会很快触及到上限。
  • 由于遇到大量的客户反馈文件句柄过多的问题,InfluxDB在新版本的存储引擎选型中选择了BoltDB替换LevelDB。BoltDB底层数据结构是mmap B+树。 但由于B+ 树会产生大量的随机写。 所以写入性能较差。
  • 之后Influxdb 最终决定仿照LSM 的思想自研TSM ,主要改进点是基于时序数据库的特性作出一些优化,包含Cache、WAL以及Data File等各个组件,也会有flush、compaction等这类数据操作。

InfluxDB 系统架构

img

DataBase: 用户可以通过 create database xxx 来创建一个database。

Retention Policy(RP): 数据保留策略, 可用来规定数据的的过期时间。

1
CREATE RETENTION POLICY "a_year" ON "food_data" DURATION 52w REPLICATION 1 SHARD DURATION 1h

上述语句可以创建一个保存周期为52周的RP。REPLICATION 1 表示副本数量为1。 ”SHARD DURATION”指定每个Shard Group对应多长时间。

一个数据库可以用多个RP , 不同的表可以设置不同的RP。

Shard Group : 实现了数据分区,但是Shard Group只是一个逻辑概念,在它里面包含了大量Shard,Shard才是InfluxDB中真正存储数据以及提供读写服务的概念。

Share :结构示意图如下:

img

Share 就是上面章节所介绍的TSM 引擎, 负责把数据写入文件。

具体的过程类似于LSM,数据来了先存到cashe, 等cashe 大小到一定程度就会异步flush 到TSM文件。 同时WAL(Write Ahead Log) 用来预防数据丢失。

TSM 原理

先来看一下Influxdb 数据模型:

img

重要概念:

  1. Measurement : 类似于mysql 的表名。 (census)
  2. tag key: 类似于mysql 加了索引的列名
  3. Field key : 类似于mysql 没加索引的列名
  4. Point:类似SQL中一行记录,而并不是一个点。
1
2
3
insert census,location=1,scientist=langstroth butterflies=12,honeybees=23 1435362189575692182 
//向census 插入一条数据,location,scientist 为 tag key ,butterflies和honeybees 为filed key ,tag 和filed 之间空格间隔。 1435362189575692182为时间戳, 可省略。
show series from census

InfluxDB 核心概念 – Series

时序数据的时间线就是一个数据源采集的一个指标随着时间的流逝而源源不断地吐出数据,这样形成的一条数据线称之为时间线。

img

InfluxDB在时序数据模型设计方面提出了一个非常重要的概念:SeriesKey。SeriesKey实际上就是measurement+datasource(tags)。时序数据写入内存之后按照SeriesKey进行组织。

时序数据在内存中表示为一个Map:<Key, List<Timestamp|Value>>, 其中Key = seriesKey + fieldKey。这个Map执行flush操作形成TSM文件。

TSM 文件结构

img

TSM文件最核心的由Series Data Section以及Series Index Section两个部分组成,其中前者表示存储时序数据的Block,而后者存储文件级别B+树索引Block,用于在文件中快速查询时间序列数据块。

Series Data Block

Map中一个Key对应一系列时序数据,因此能想到的最简单的flush策略是将这一系列时序数据在内存中构建成一个Block并持久化到文件。然而,有可能一个Key对应的时序数据非常之多,导致一个Block非常之大,超过Block大小阈值,因此在实际实现中有可能会将同一个Key对应的时序数据构建成多个连续的Block。但是,在任何时候,同一个Block中只会存储同一种Key的数据。

另一个需要关注的点在于,Map会按照Key顺序排列并执行flush,这是构建索引的需求。Series Data Block文件结构如下图所示:

img

Series Index Block

每个key 对应一个Index Block。

很多时候用户需要根据Key查询某段时间(比如最近一小时)的时序数据,如果没有索引,就会需要将整个TSM文件加载到内存中才能一个Data Block一个Data Block查找,这样一方面非常占用内存,另一方面查询效率非常之低。为了在不占用太多内存的前提下提高查询效率,TSM文件引入了索引,其实TSM文件索引和HFile文件索引基本相同。TSM文件索引数据由一系列索引Block组成,每个索引Block的结构如下图所示:

img

Series Index Block由Index Block Meta以及一系列Index Entry构成:

  1. Index Block Meta最核心的字段是Key,表示这个索引Block内所有IndexEntry所索引的时序数据块都是该Key对应的时序数据。
  2. Index Entry表示一个索引字段,指向对应的Series Data Block。指向的Data Block由Offset唯一确定,Offset表示该Data Block在文件中的偏移量,Size表示指向的Data Block大小。Min Time和Max Time表示指向的Data Block中时序数据集合的最小时间以及最大时间,用户在根据时间范围查找时可以根据这两个字段进行过滤。

TSM引擎工作原理-时序数据读取

基于对TSM文件的了解,在一个文件内部根据Key查找一个某个时间范围的时序数据就会变得很简单,整个过程如下图所示:

img

上图中中间部分为索引层,TSM在启动之后就会将TSM文件的索引部分加载到内存,数据部分因为太大并不会直接加载到内存。用户查询可以分为三步:

  1. 首先根据Key找到对应的SeriesIndex Block,因为Key是有序的,所以可以使用二分查找来具体实现。
  2. 找到SeriesIndex Block之后再根据查找的时间范围,使用[MinTime, MaxTime]索引定位到可能的Series Data Block列表。
  3. 将满足条件的Series Data Block加载到内存中解压进一步使用二分查找算法根据timestamp查找即可找到。

多维查询之倒排索引

上个章节讲的是如何根据key 查到数据。 然而,在实际场景中, 经常会有根据表名和 部分tag 来查询数据的场景。 比如最开始的那个表, 用户要查cenus 表 location=1 在一周内的所有butterflies 总数。 这种的话如果tag 和measurement 不做索引的话,则查询方式只能是遍历对比。

InfluxDB给出了倒排索引的实现,称之为TimeSeries Index,意为TimeSeries的索引,简称TSI。InfluxDB TSI在1.3版本之前仅支持Memory-Based Index实现,1.3之后又实现了Disk-Based Index实现。

Memory-Based Index

Memory-Based Index方案将所有TimeSeries索引加载到内存提供服务,核心数据结构主要有:

img

  • seriesByID:通过SeriesID查询Series的一个Map。
  • seriesByTagKeyValue:双层Map,第一层是TagKey对应其所有的TagValue,第二层是TagValue对应的所有Series的ID。可以看到,当TimeSeries的基数变得很大,这个map所占的内存会相当多。
  • sortedSeriesIDs:一个排序的SeriesID列表。

Q:考虑下如何查询多个tag 纬度下的series ID?A:答案是拉出多个 seriesID List 求交集。

Disk-Based Index

Disk-baesd index 的实现思路和TSM file 的实现原理一样。 先写入WAL 预写日志,然后数据存入cashe , 当cashe 积攒到一定大小后, flush 到文件。

Q: 新插入一条数据, 如何判断series key 是否已经存在? A:Bloom Filter

常用操作

下载 :

1
2
wget https://dl.influxdata.com/influxdb/releases/influxdb_1.7.9_amd64.deb
sudo dpkg -i influxdb_1.7.9_amd64.deb

启动:

1
2
./influxd 启动influxdb 服务器。 ./influx 启动shell 客户端。 
远程连接服务端 ./influx -host XXX -port xx

创建数据库:

1
2
create database monit
use monit

插入数据:

1
2
insert census,location=1,scientist=langstroth butterflies=12,honeybees=23
//无需先创建measurement

查询数据:

1
select * from census

http 接口访问:

1
curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=mydb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"

q= 除了可以接查询语句, 还可以接建RP, 建CQ , 插入等等操作

支持同时多个点写入和同时多个查询

1
2
curl -i -XPOST 'http://localhost:8086/write?db=mydb' --data-binary 'cpu_load_short,host=server02 value=0.67 cpu_load_short,host=server02,region=us-west value=0.55 1422568543702900257
cpu_load_short,direction=in,host=server01,region=us-west value=2.0 1422568543702900257'

支持连续查询(Continuous Queries简称CQ) 来处理数据采样。

假设我有一个数据库monitor , 里面有一张表monit.biz.douyin 记录抖音的数据打点,tag(metric,province,carrier)。 我之前创建了一个名叫3_month的RP ,我想按照metric 和province 两个tag聚合每分钟抖音的打点数量 。建立CQ 如下

1
CREATE CONTINUOUS QUERY "douyin_1min_sum" ON "monitor" BEGIN SELECT sum("count") INTO monitor."3_month"."monit.biz.douyin.1min" FROM monitor."default"."monit.biz.douyin" GROUP BY time(1m),metric,province END

monit.biz.douyin.1min 就是聚合完的新表。