学习influxdb
简介
当今时代计算机技术日新月异,各种新颖的场景对存储提出了各式各样的要求,涌现了各种适用于特殊数据类型的数据库。
特定领域数据库:为特定的数据类型进行专门优化。
- 时序数据库:TSDB,Time Series Database
- 空间数据库:存储地理位置信息
- 向量数据库:例如人脸识别中把人脸转化为向量,视频消重把视频转化为向量
- 文本数据库:如Solr和ElasticSearch,可以对文本进行更完善的索引
- ......
时序数据的特点:
- 持续地稳定地产生着数据,对时序数据库的请求qps不存在波峰波谷,数据采集器会定时上报采集到的数据
- 写远远大于读
- 时效性明显,读总是关注最近一段时间
- 一段时间内的数据具有聚合价值
- 多维,也就是多个tag
常见时序数据库:
- influxdb:go语言实现的高性能时序数据库。
- OpenTSDB:Opentsdb是一个基于Hbase的时间序列数据库
- promeseus: Prometheus 是一个监控系统,它不仅仅包含了时间序列数据库,还有全套的抓取、检索、绘图、报警的功能。仅支持单机,数据写入本地。
- graphite
- Druid是一个实时在线分析系统(LOAP)。其架构融合了实时在线数据分析,全文检索系统和时间序列系统的特点,使其可以满足不同使用场景的数据存储需求。
- Beringei是Facebook在2017年最新开源的一个高性能内存时序数据存储引擎。其具有快速读写和高压缩比等特性。2015年Facebook发表了一篇论文《Gorilla: A Fast, Scalable, In-Memory Time Series Database 》,Beringei正是基于此想法实现的一个时间序列数据库。Beringei使用Delta-of-Delta算法存储数据,使用XOR编码压缩数值。使其可以用很少的内存即可存储下大量的数据。
- Elasticsearch 是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名。
- RedisTimeSeries 是 Redis 的一个扩展模块。它专门面向时间序列数据提供了数据类型和访问接口,并且支持在 Redis 实例上直接对数据进行按时间范围的聚合计算。
- DophinDb:国产时序数据库。DolphinDB是一套轻量级的分布式数据库系统,具有非常高的写入吞吐量和非常低的查询时延,面对万亿级的数据量,可以实现毫秒级的在线查询延迟。同时也支持高可用和分布式事务。DolphinDB自带流计算功能,可以实时接入题主所说的行情数据,展开实时计算,并将原始数据和计算后的数据存入数据仓库。
时序数据收集工具:
- Telegraf是一个用于采集和上报指标的服务器程序,采集当前运行主机的指定指标,如,CPU负载等,通过标准的InfluxDB API上报InfluxDB。
- LogStash
时序数据处理相关的整条链路:
- 数据收集系统:telegraf、logStash
- 数据存储系统:influxdb
- 数据分析系统:bosun,bes
- 数据展示系统:grafana
时序数据库选型
时序数据库排名:
如何选择一个适合自己的时间序列数据库
- Data model:时间序列数据模型一般有两种,一种无schema,具有多tag的模型,还有一种name、timestamp、value型。前者适合多值模式,对复杂业务模型更适合。后者更适合单维数据模型。
- Query language:目前大部分TSDB都支持基于HTTP的SQL-like查询。
- Reliability:可用性主要体现在系统的稳定高可用上,以及数据的高可用存储上。一个优秀的系统,应该有一个优雅而高可用的架构设计。简约而稳定。
- Performance:性能是我们必须考虑的因素。当我们开始考虑更细分领域的数据存储时,除了数据模型的需求之外,很大的原因都是通用的数据库系统在性能上无法满足我们的需求。大部分时间序列库倾向写多读少场景,用户需要平衡自身的需求。下面会有一份各库的性能对比,大家可以做一个参考。
- Ecosystem:我一直认为生态是我们选择一个开源组件必须认真考虑的问题。一个生态优秀的系统,使用的人多了,未被发现的坑也将少了。另外在使用中遇到问题,求助于社区,往往可以得到一些比较好的解决方案。另外好的生态,其周边边界系统将十分成熟,这让我们在对接其他系统时会有更多成熟的方案。
- Operational management:易于运维,易于操作。
- Company and support:一个系统其背后的支持公司也是比较重要的。背后有一个强大的公司或组织,这在项目可用性保证和后期维护更新上都会有较大的体验。
性能对比
可以按照以下需求自行选择合适的存储:
- 小而精,性能高,数据量较小(亿级): InfluxDB
- 简单,数据量不大(千万级),有联合查询、关系型数据库基础:timescales
- 数据量较大,大数据服务基础,分布式集群需求: opentsdb、KairosDB
- 分布式集群需求,olap实时在线分析,资源较充足:druid
- 性能极致追求,数据冷热差异大:Beringei
- 兼顾检索加载,分布式聚合计算: elsaticsearch
- 如果你兼具索引和时间序列的需求。那么Druid和Elasticsearch是最好的选择。其性能都不差,同时满足检索和时间序列的特性,并且都是高可用容错架构。
awesome-time-series-database
时序数据库百花齐放、百家争鸣,有一个专门的网站用于比较各个时序数据库:xephonhq.github.io/awesome-tim…
本文主要讲influxdb。
安装
mac下直接使用brew命令安装influxdb
brew update
brew install influxdb
pip install influxdb //安装Python包,操作influxdb
运行
influxd # 启动server
influx # 启动client,进入交互式命令行
show databases;//查看全部数据库
use mydb;//进入某个数据库
show measurements;//查看数据库下的全部表名
默认数据和配置存储在~/.influxdb目录下。
和所有的数据库一样,influxdb支持交互式命令行和API两种客户端。
influxd命令其实就相当于启动了一个HTTP服务,它的默认端口是8086。influxdb的API客户端都是通过HTTP的形式与服务器交互。
Influxdb支持RESTful风格的接口,返回JSON格式的响应数据,并支持身份认证、JWT令牌、丰富的HTTP响应代码等。
influxDB API接口及接口的定义描述如下图所示:
curl -ig http://192.168.0.224:8086/ping
概念
database:数据库
measurement:相当于表名,measurement字符串必须是一个合法的标志符,不能包含问号等特殊字符
point:一个point相当于一个时刻对应的记录,一个记录有timestamp,tags,values三部分组成,一个measurement就是由很多条记录组成的。
timestamp:时间戳,influxdb支持纳秒级别的时间戳,时间戳在influxdb的索引和查询中占据着至关重要的位置。同一个measurement,如果插入两条tags和时间戳都相同的数据,会发生数据覆盖。
tags:一个measurement有若干个tags,tags可以看做一个map[string]string结构。
列1、列2、列3....:各个字段及其对应的取值,表示当前时间系统表现出来的指标数值。
retention_policy:存储策略,定义历史数据的删除间隔。可以为整个数据库创建默认过期间隔,也可以为每个measurement定义存储策略。
series:不可再细分的线的集合,series=RetentionPolicy+measurement+tags。series 的 key 为 measurement + tags set 序列化字符串。series 相当于是 InfluxDB 中一些数据的集合,在同一个 database 中,retention policy、measurement、tag sets 完全相同的数据同属于一个 series,同一个 series 的数据在物理上会按照时间顺序排列存储在一起。
type Measurement struct {
Name string `json:"name,omitempty"`
fieldNames map[string]struct{} // 此 measurement 中的所有 filedNames
// 内存中的索引信息
// id 以及其对应的 series 信息,主要是为了在 seriesByTagKeyValue 中存储Id节约内存
seriesByID map[uint64]*Series // lookup table for series by their id
// 根据 tagk 和 tagv 的双重索引,保存排好序的 SeriesID 数组
// 这个 map 用于在查询操作时,可以根据 tags 来快速过滤出要查询的所有 SeriesID,之后根据 SeriesKey 以及时间范围从文件中读取相应内容
seriesByTagKeyValue map[string]map[string]SeriesIDs // map from tag key to value to sorted set of series ids
// 此 measurement 中所有 series 的 id,按照 id 排序
seriesIDs SeriesIDs // sorted list of series IDs in this measurement
}
type Series struct {
mu sync.RWMutex
Key string // series key
Tags map[string]string // tags
id uint64 // id
measurement *Measurement // 这个series所属于的父measurement
}
如上述代码所示,在Measurement结构体中:
- 使用fieldNames来表示这个表所包含的全部字段,golang中没有Set结构,这里直接使用map[string]struct{}代替Set结构。
- 使用seriesByTagKeyValue这个二维表来存储tagKV到Series的映射。seriesByTagKeyValue是一个二维表,第一维表示TagKey,第二维表示TagValue。当插入一条包含one=a,two=b两个tag的记录时,会创建一个包含one=a,two=b的Series。然后seriesByTagKeyValue['one']['a'].append(新创建的series),seriesByTagKeyValue['two']['b'].append(新创建的series)。当再插入一个包含one=a的记录时,创建一个series2,然后seriesByTagKeyValue['one']['a'].append(series2)。在以上插入过程中,Series如果已经存在,则不会执行重复的创建Series过程,SeriesIDs这个Series列表也会执行去重操作。
当查询包含多个tagKV时,会首先根据seriesByTagKeyValue求出所有的seriesIDs,这个操作就是就seriesIDs的交集。
- SeriesIDs:在上述seriesByTagKeyValue中,表中的value是一个Series列表,每个Series使用一个SeriesID进行表示。seriesID是一个int64.
map[string]map[string]SeriesIDs
每一条记录都可以使用“行协议”用一行字符串表示,行协议的单行文本表示一条时序数据,由表名、tag集、指标集和时间戳4部分组成,行协议的基本语法如下所示:
如下表所示,展示了一个measurement中的数据
| 时间戳 | Tag | Value | |||
|---|---|---|---|---|---|
| 时间戳 | 城市 | 气象台 | 风级 | 气温 | 天气 |
| 2020-13-01 10:18 | 北京 | 中国气象台 | 1 | 12.5 | 大雨 |
| 2020-13-01 10:19 | 北京 | 美国气象台 | 2 | 32.4 | 大雪 |
| 2020-13-01 10:20 | 天津 | 中国气象台 | 1.5 | 22.3 | 晴 |
基于这个measurement可以进行查询:
- 一段时间内,中国气象台预测的北京的天气状况
- 一段时间内,各个气象台预测的北京的平均气温
如下图,tags是device_id,values包括cpu、内存、温度、位置等信息。
特点
- schemaless:无需定义表结构,像mongodb一样直接存储。
- 上手门槛低:基于SQL语句进行查询,节省学习成本。SQL-like查询语言,简化查询和聚合操作。InfluxDB自带多种函数使数据统计变得十分方便。
- 基于go语言实现,并发支持良好
- 高效的时间序列数据写入性能。自定义TSM引擎,快速数据写入和高效数据压缩。
- influxdb存储基于文件系统,无额外存储依赖(OpenTSDB:我就用HBase怎么了?)。
- 简单,高性能的HTTP查询和写入API。
- 以插件方式支持多种协议,与各种数据收集工具、数据展示工具配合流畅。
- 索引Tags,支持快速有效的查询时间序列。注意:tags过多会拖慢性能,例如不要为了记录用户修改个人信息的次数,把userId=xxxx作为tag。任何tagValue取值太多的字段都不应该被当做tag。
- 存储策略有效去除过期数据。
- 支持连续查询,连续查询自动计算聚合数据,使频繁查询更有效。
- 支持丰富的数据类型(注意:整型数据,需要在数据后面加个i,否则会被当成浮点型)
- 缺点:分布式版本闭源,不够开放,分布式集群需要购买或者自研。
InfluxQL
influxdb支持类sql的语法进行数据查询,这种语言简称InfluxQL。
InfluxQL支持SELECT语句、GROUP BY语句、INTO语句、正则表达式、SHOW语句、数据库管理语句、保留策略管理语句、DROP语句、持续查询、各种函数和数学运算符等。
基本查询
# 基本查询
select cpu,mem from my_measurement;
# 查询多个measurement
select cpu,mem from my_measurement1,my_measurement2;
# 查询特定数据库
select cpu,mem from mydb.my_measurement;
where查询
select * from my_measurement where time>now()-1d and host='localhost'
where语句中可以使用or和and连接多个条件。
group by语句
group by
group by ,
group by time()
group by time(),
order by语句
order by time [ ASC | DESC ]
select * from cpu_usage where hostname='localhost' and time>now()-1d and percent>10 order by time desc limit 3
limit和offset,slimit和soffset
limit和offset对记录进行限制
slimit和soffset对分组进行限制
例如获取cpu_usage中第10个host到第20个host的指标数据的第100条到第200条数据
select * from cpu_usage where time>now()-1d group by hostname limit 100 offset 100 slimit 10 soffset 10
influxdb中的时间描述
绝对时间:
-
rfc3339时间字符串:YYYY-MM-DDTHH:MM:SS.nnnnnnnnnZ,其中.nnnnnnnnnZ部分是可选的。
-
简化版rfc3339时间字符串:YYYY-MM-DD HH:MM:SS.nnnnnnnnn,其中纳秒部分也是可选的
-
使用“<数字><单位>”的方式描述时间,表示自从1970年1月1日到现在过去了多少时间。influxdb支持的时间单位包括:
- ns 纳秒,如果是纳秒可以不带单位,默认单位就是纳秒。
- u 微秒
- ms 毫秒
- s 秒
- m 分钟
- h小时
- d天
- w周
select * from cpu_usage where time>123414s;
select * from cpu_usage where time>'2019-08-26T19:02:00Z';
相对时间:使用now()函数获取当前时间,然后加减一个时间段,时间段的单位如上所述。
函数
influxdb中的函数包括
- 聚合函数
- 选择函数
- 变换函数
select count(distinct(value)) from cpu_usage;
select mean(value) from cpu_usage where host='localhost';
选择函数包括:
- bottom():最小的n个指标值
- top():最大的n个指标值
- max(),min(),sample(),percentile():对value进行选择,取最值、百分比值、随机采样等。
- first(),last():返回时间最旧和时间最新的两个记录
select TOP(value,4) from cpu_usage
变换函数包括:
- DERIVATIVE() 求导数
- DIFFERENCE() 直接计算差
- ELAPSED()
- MOVING_AVERAGE() :滑动平均
- NON_NEGATIVE_DERIVATIVE() :非负变化率
- STDDEV() :标准差
插入语句
插入语句的格式为insert ,=,= =,=
insert devops-idc-sz,host=server01 cpu=76.1,mem=0.83 1567158293000000000
bes中的查询
bes的查询语句
SELECT sum("value") FROM "sc_0_go_goroutine_cn" WHERE time>1602569220s AND time<1602570420s GROUP BY time(30s) fill(none)
删除数据
# 删除记录
delete from devops-idc-sz where "host"='server01' and time=1567158293s
# 删除包含特定tag的记录
drop series from devops-idc-sz where "host"='server01'
# 删除一张表
drop measurement devops-idc-sz
# 删除一个数据库
drop database mydb
元数据查询
查询全部的measurements:SHOW MEASUREMENTS
查询全部的tagKeys:SHOW TAG KEYS FROM "measurement_name"
查询tagValues:SHOW TAG VALUES FROM "measurement_name" WITH KEY = "tag_key"
存储策略
存储策略是influxdb中至关重要的概念,一个influxdb数据库首先可以分为若干个存储策略,每个存储策略下面按照时间划分为若干个ShardGroup。
- 数据过期是以shardGroup为单位执行的
- 一个RetentionPolicy可以作用于多个ShardGroup
- Shard是influxdb执行写入和查询的基本单位。Shard是InfluxDB的存储引擎实现,influxdb的存储引擎被称为TSM(Time Sort Merge Tree) Engine,负责数据的编码存储、读写服务等。TSM类似于LSM,因此Shard和HBase Region一样包含Cache、WAL以及Data File等各个组件,也会有flush、compaction等这类数据操作。
shard 在 InfluxDB 中是一个比较重要的概念,它和 retention policy 相关联。每个存储策略下会存在许多 shard,每个 shard 存储一个指定时间段内的数据,而且不重复,例如 7点-8点的数据落入 shard0 中,8点-9点的数据则落入 shard1 中。每个 shard 都对应一个底层的 tsm 存储引擎,有独立的 cache、wal、tsm file。
映射关系:
- ShardGroup按照时间划分
- 一个ShardGroup内的Shard按照measurement+tags划分
RetentionPolicy意思是数据存储策略。不要以为RP只规定了数据的过期时间,RP在InfluxDB中是一个非常重要的概念,核心作用有3个:
- 指定数据的过期时间
- 指定数据副本数量
- 指定每个分片大小:Shard Duration。
RP创建语句如下:
CREATE RETENTION POLICY ON <retention_policy_name> ON <database_name> DURATION <duration> REPLICATION <n> [SHARD DURATION <duration> ] [DEFAULT]
# demo
CREATE RETENTION POLICY "one_day_only" ON "water_database" DURATION 1d REPLICATION 1 SHARD DURATION 1h DEFAULT
# 查询一个数据库上的全部保留策略
show retention policies on telegraf
# 更改保留策略
alter retention policy "rp-one-year" on "telegraf" duration 365d replication 1 default
# 删除保留策略
drop retention policy "rp-one-year" on "telegraf"
其中retention_policy_name表示RP的名称,database_name表示数据库名称,duration表示TTL,n表示数据副本数。
InfluxDB中Retention Policy有这么几个性质和用法:
-
RP是数据库级别而不是表级别的属性。这和很多数据库都不同。
-
每个数据库可以有多个数据保留策略,但只能有一个默认策略。
-
不同表可以根据保留策略规划在写入数据的时候指定RP进行写入
存储引擎
influxdb在LSM基础上实现了TSM用来管理数据存储。
CentOS下influxdb默认配置路径为/etc/influxdb/influxdb.conf,influxdb数据存储主要有3个目录,分别是meta、wal和data。meta主要存放元数据,该目录下有一个meta.db文件;wal目录存放预写日志,以.wal结尾;data目录存放TSM文件,以.tsm结尾,持久化的数据就存储在这里。
在同一个database中,retention policy、measurement、tag kv 完全相同的数据属于同一个 series(series 的 key 为 measurement + tags set 序列化字符串),同一个 series 的数据在物理上会按照时间顺序排列存储在一起。
外部接口和内部存储的关系
influxdb支持多个field,但是存储上influxdb会把它们分开存储。influxdb的内部存储只考虑一个field的情况。当查询时,如果查询多个field,会逐个处理每一个field,然后把它们拼起来。
influxdb存储上以series的key作为主键进行存储,一次查询可能会变成查找多个series。当处理一个包含多个tags时,会根据多个tagKV求seriesIDs的交集,最终只查找合并后的seriesIDs,然后把它们进行拼接。一旦进入查询部分,measurement名称和tags就被seriesID替代了。
WAL
wal(Write Ahead Log)是一种格式固定、写入速度超快的文件,它只支持append操作。
WAL文件的作用:防止宕机导致内存中数据丢失。
influxdb的WAL就是一系列格式为 _00xxx.wal 的文件,文件号单调递增,默认当超过10M时就会新写一个WAL文件,每个WAL文件都会存储经过压缩的数据。简言之,wal文件就是固定大小的日志文件。
当WAL日志对应的数据被写入到TSM中后,WAL日志就可以删除了。
wal每条记录的格式:
- Type (1 byte) : 表示这个条目中 value 的数据类型,虽然influxdb支持多列存储,但是influxdb底层实现是多列拆开存储的,因此此处Type只表示一列的FieldName。
- Key Len (2 bytes) : 指定接下来一个字符串key 的长度。
- Key (N bytes) : 这里的 key 为 measument + tags + fieldName,也可以看做series+fieldName。
- Count (4 bytes) : 表示接下来有多少个time、value对,表示同一个 key 下每个时间点的取值。
- Time (8 bytes) : int64,单个 value 的时间戳,不管上层用什么时间单位,存储上都用int64。
- Value (N bytes) : value 的具体内容,其中 float64, int64, boolean 都是固定的字节数存储比较简单,经过 Type 字段知道这里 value 的字节数。string 类型比较特殊,对于 string 来讲,N bytes 的 Value 部分,前面 4 字节用于存储 string 的长度,剩下的部分才是 string 的实际内容。
注意:influxdb在存储数据的时候虽然支持多列,但是实际存储时列与列之间是分开存储的。查询的列越多,耗费时间线性增长。
当一个新的Point数据被写入时,首先经过压缩写入到WAL中,然后会写入到内存的索引中,这意味着数据写入后立马可通过索引可见。
Cache
Cache就是WAL的内存表示,它在运行时可被查询并且与TSM中保存的文件进行合并。
当缓存大小超过 cache-snapshot-memory-size 时会触发缓存数据写入到TSM文件,并删除对应的WAL段文件;当缓存大小超过 cache-max-memory-size 时,cache拒绝新的写入,避免内存不够用造成宕机。
除了内存的阈值限制之外,缓存还会在 cache-snapshot-write-cold-duration 配置的时间间隔定期将缓存数据写入到TSM文件。通过重读所有WAL文件,Influxdb可以在启动时重建缓存。
Cache就是WAL的内存表示,在内存中它就是一个map结构,其key就是 series key+fieldName,seriesKey就是measurement?tag1=value1&tag2=value2这样的字符串。
type Cache struct {
commit sync.Mutex
mu sync.RWMutex
store map[string]*entry
size uint64 // 当前使用内存的大小
maxSize uint64 // 缓存最大值
// snapshots are the cache objects that are currently being written to tsm files
// they're kept in memory while flushing so they can be queried along with the cache.
// they are read only and should never be modified
// memtable 快照,用于写入 tsm 文件,只读
snapshot *Cache
snapshotSize uint64
snapshotting bool
// This number is the number of pending or failed WriteSnaphot attempts since the last successful one.
snapshotAttempts int
stats *CacheStatistics
lastSnapshot time.Time
}
综合理解Cache和WAL:
- 插入数据就是往WAL和Cache写数据
- wal文件只是起到宕机恢复的作用
- 当influxdb启动时,会遍历所有WAL文件构建Cache,这样保证系统出现故障也不会造成数据丢失。
TSM文件
每个shard都有自己独立的TSM存储引擎,都有自己独立的TSM文件,单个TSM文件大小最大为 2GB,用于长期存放数据。
TSM文件是influxdb数据存储的一系列只读文件集合,这些文件结构类似于leveldb中的SSTable,一个TSM文件格式如下:
- Header:头部信息,4Byte magic字段+1Byte version字段。
- Blocks:CRC(校验值)+数据存储字段,数据的长度在index字段存储。Blocks内部是一些连续的 Block,Block 是 InfluxDB 中的最小读取对象,每次读取操作都会读取一个 Block。每一个 Block 分为 CRC32 值和 Data 两部分,CRC32 值用于校验 Data 的内容是否有问题。Data 的长度记录在之后的 Index 部分中。
- Index:索引按照(key,时间戳)来排序,key=measurement+tags+一个fieldName=sereisKey+fieldName。每个索引条目以key len和key开始(因为key是一个字符串,所以需要keyLen字段),然后是block的数据类型(例如可取值包括float,int,bool,string等,表示当前列的数据类型)以及该block包含的数据记录个数,之后是block的最小、最大时间戳(这对于按照时间检索至关重要),最后是block所在的文件偏移量以及block大小。TSM文件中每个block都有一个索引block条目,用于存储Block的元信息,从而可以快速定位Block。
- footer:它是一个定长结构,存储了索引开头的offset,从而可以确定Blocks区域和Index区域的分界点。解析TSM文件时,首先读取定长的header确定版本号,这决定了采用哪种解析器。然后读取定长的footer来确定Index和Blocks分界点。最后读取Index信息来获取Blocks部分的数据格式,才能组成一个完整的TSM索引+数据信息。
Block对应的数据是经过压缩的,以减少存储空间。Block第一个字节表示当前列的数据类型,接下来是一个变长的整数(根据数值范围动态调整需要用几个字节来存储这个整数,类似redis),这个整数表示接下来的timestamp+value对数,最后是若干个Timestamp+Values。
influxdb会针对不同类型数据采用不同压缩编码,比如时间戳、整型、浮点数和字符串等,字符串使用Snappy压缩进行编码,每个字符串连续打包然后压缩成一个较大的块。
influxdb的底层查询可以归结为:给定seriesKey,查找timestamp在某个[beginTime,endTime]这个区间上fieldName字段上的取值。
TSM文件中Index字段是有序的,它按照(seriesKey+fieldName,beginTime)这个二元组进行有序排列,因此所有的查询最终都会变成二分查找。
如果能够把Index区域全部加载到内存,查询速度确实会快。但是Influxdb认为Index区域依旧太大,也是超出内存限制的。所以对于一个TSM文件,Influxdb需要在不把Index完全加载到内存的情况下执行查询。
但是TSM文件中的Index区域中每个index是不定长的,无法执行二分查找(二分查找需要随机访问硬盘),所以需要在内存中构建index区域的offsets数组,表示每个index在Index区域中的偏移量,然后通过“offsets数组+硬盘读取”执行二分查找。
找到了index之后,就可以根据index的详情找到要查询的数据在Blocks区域中的内容(index详情中描述了block的偏移量和长度)。
删除操作
数据删除在influxdb中是一个低效操作,特别是针对大数据量删除来说,并且只有等待数据合并时才会真正删除数据。
删除数据时,需要更新三个地方:
- WAL中内容的删除:数据删除通过向WAL写入删除条目
- Cache中的删除是在内存中直接删除
- TSM文件写入一个tombstone文件(墓碑文件),这些tombstone文件被用于在启动时忽略相应的block,以及在compaction时期移除已删除的数据。
一次查询过程
接下来从头讲一遍一次数据查询的过程,把存储引擎和基本概念串联起来。
select one,two FROM cpu WHERE host='s01' and region='cn' AND time > now() - 10h
从measurement结构体中的seriesByTagKeyValue根据映射获取全部的seriesIDs:对seriesByTagKeyValue['host']['s01']和seriesByTagKeyValue['region']['cn']执行集合求交集,得到一个seriesIDs,表示一个series列表。然后构建key,构建方法就是:key列表=series列表*field列表。最终,所有的查询都是基于key进行查询,然后对查询结果执行merge操作返回给用户。
keyId,measurement结构体如下:
type Measurement struct {
Name string `json:"name,omitempty"`
fieldNames map[string]struct{} // 此 measurement 中的所有 filedNames
// 内存中的索引信息
// id 以及其对应的 series 信息,主要是为了在 seriesByTagKeyValue 中存储Id节约内存
seriesByID map[uint64]*Series // lookup table for series by their id
// 根据 tagk 和 tagv 的双重索引,保存排好序的 SeriesID 数组
// 这个 map 用于在查询操作时,可以根据 tags 来快速过滤出要查询的所有 SeriesID,之后根据 SeriesKey 以及时间范围从文件中读取相应内容
seriesByTagKeyValue map[string]map[string]SeriesIDs // map from tag key to value to sorted set of series ids
// 此 measurement 中所有 series 的 id,按照 id 排序
seriesIDs SeriesIDs // sorted list of series IDs in this measurement
}
在查询过程中,需要经过RetentionPolicy+ShardGroup+Shard三层映射,这些映射都是根据时间戳来的,从而把查询key的任务分发给了各个shard,问题转化为在shard内部的查询。
当对一个shard进行查询时,加载shard对应的tsm文件中的index部分,在内存中构建索引,执行二分查找确定key+timestamp所对应的范围,得到beg和end。在tsm文件的index部分获取索引的详情,从而得到block的地址,解压各个block就得到时序数据。
Compactor
compactor是压缩器,在后台持续运行,每隔 1 秒会检查一次是否有需要压缩合并的数据。
它主要进行两种操作:
- Cache=>TSM文件。当 cache 中的数据大小达到阀值后,进行快照,以后转存到一个新的 tsm 文件中。
- 合并压缩TSM文件:将多个小的 tsm 文件合并成一个,使每个文件尽可能达到单个文件的最大大小,减小文件的数量,而且一些数据的删除操作也是在这个时候完成。
数据压缩过程是一个将write-optimized格式优化为read-optimized的格式的过程,Influxdb的压缩包括多种压缩过程。
- LevelCompaction:InfluxDB将TSM文件分为4个层级(Level 1-4),compaction只会发生在同层级文件内,同层级的文件compaction后会晋升到下一层级。从这个规则看,根据时序数据的产生特性,level越高数据生成时间越旧,访问热度越低。由Cache数据初次生成的TSM文件称为Snapshot,多个Snapshot文件compaction后产生Level1的TSM文件,Level1的文件compaction后生成level2的文件,依次类推。低Level和高Level的compaction会采用不同的算法,低level文件的compaction采用低CPU消耗的做法,例如不会做解压缩和block合并,而高level文件的compaction则会做block解压缩以及block合并,以进一步提高压缩率。
- IndexOptimizationCompaction: 当Level4的文件积攒到一定个数后,index会变得很大,查询效率会变的比较低。影响查询效率低的因素主要在于同一个TimeSeries数据会被多个TSM文件所包含,所以查询不可避免的需要跨多个文件进行数据整合。所以IndexOptimizationCompaction的主要作用就是将同一TimeSeries下的数据合并到同一个TSM文件中,尽量减少不同TSM文件间的TimeSeries重合度。
- FullCompaction: InfluxDB在判断某个Shard长时间内不会再有数据写入之后,会对数据做一次FullCompaction。FullCompaction是LevelCompaction和IndexOptimization的整合,在做完一次FullCompaction之后,这个Shard不会再做任何的compaction,除非有新的数据写入或者删除发生。这个策略是对冷数据的一个规整,主要目的在于提高压缩率。
配置
reporting-disabled = false
bind-address = "127.0.0.1:8088"
[meta]
dir = "/Users/bytedance/.influxdb/meta"
retention-autocreate = true
logging-enabled = true
[data]
dir = "/Users/bytedance/.influxdb/data"
index-version = "inmem"
wal-dir = "/Users/bytedance/.influxdb/wal"
wal-fsync-delay = "0s"
validate-keys = false
query-log-enabled = true
cache-max-memory-size = 1073741824
cache-snapshot-memory-size = 26214400
cache-snapshot-write-cold-duration = "10m0s"
compact-full-write-cold-duration = "4h0m0s"
compact-throughput = 50331648
compact-throughput-burst = 50331648
max-series-per-database = 1000000
max-values-per-tag = 100000
max-concurrent-compactions = 0
max-index-log-file-size = 1048576
series-id-set-cache-size = 0
series-file-max-concurrent-snapshot-compactions = 0
trace-logging-enabled = false
tsm-use-madv-willneed = false
[coordinator]
write-timeout = "10s"
max-concurrent-queries = 0
query-timeout = "0s"
log-queries-after = "0s"
max-select-point = 0
max-select-series = 0
max-select-buckets = 0
[retention]
enabled = true
check-interval = "30m0s"
[shard-precreation]
enabled = true
check-interval = "10m0s"
advance-period = "30m0s"
[monitor]
store-enabled = true
store-database = "_internal"
store-interval = "10s"
[subscriber]
enabled = true
http-timeout = "30s"
insecure-skip-verify = false
ca-certs = ""
write-concurrency = 40
write-buffer-size = 1000
[http]
enabled = true
bind-address = ":8086"
auth-enabled = false
log-enabled = true
suppress-write-log = false
write-tracing = false
flux-enabled = false
flux-log-enabled = false
pprof-enabled = true
pprof-auth-enabled = false
debug-pprof-enabled = false
ping-auth-enabled = false
prom-read-auth-enabled = false
https-enabled = false
https-certificate = "/etc/ssl/influxdb.pem"
https-private-key = ""
max-row-limit = 0
max-connection-limit = 0
shared-secret = ""
realm = "InfluxDB"
unix-socket-enabled = false
unix-socket-permissions = "0777"
bind-socket = "/var/run/influxdb.sock"
max-body-size = 25000000
access-log-path = ""
max-concurrent-write-limit = 0
max-enqueued-write-limit = 0
enqueued-write-timeout = 30000000000
[logging]
format = "auto"
level = "info"
suppress-logo = false
[[graphite]]
enabled = false
bind-address = ":2003"
database = "graphite"
retention-policy = ""
protocol = "tcp"
batch-size = 5000
batch-pending = 10
batch-timeout = "1s"
consistency-level = "one"
separator = "."
udp-read-buffer = 0
[[collectd]]
enabled = false
bind-address = ":25826"
database = "collectd"
retention-policy = ""
batch-size = 5000
batch-pending = 10
batch-timeout = "10s"
read-buffer = 0
typesdb = "/usr/share/collectd/types.db"
security-level = "none"
auth-file = "/etc/collectd/auth_file"
parse-multivalue-plugin = "split"
[[opentsdb]]
enabled = false
bind-address = ":4242"
database = "opentsdb"
retention-policy = ""
consistency-level = "one"
tls-enabled = false
certificate = "/etc/ssl/influxdb.pem"
batch-size = 1000
batch-pending = 5
batch-timeout = "1s"
log-point-errors = true
[[udp]]
enabled = false
bind-address = ":8089"
database = "udp"
retention-policy = ""
batch-size = 5000
batch-pending = 10
read-buffer = 0
batch-timeout = "1s"
precision = ""
[continuous_queries]
log-enabled = true
enabled = true
query-stats-enabled = false
run-interval = "1s"
[tls]
min-version = ""
max-version = ""
参考资料
中文入门简介:jasper-zhang1.gitbooks.io/influxdb/co…
英文官方Python文档:www.influxdata.com/blog/gettin…
Python教程:influxdb-python.readthedocs.io/en/latest/e…
时序数据库对比:www.cnblogs.com/dhcn/p/1297…
DolphinDB教程:dolphindb/Tutorials_CN 下载地址:DolphinDB官网
influxdb教程,举个例子网:www.hellodemos.com/hello-influ…
OpenTSDB:developer.aliyun.com/article/104…
开源时序数据库解析:developer.aliyun.com/article/106…
influxdb原理详解:www.shangmayuan.com/a/059faa17b…
influxdb查询详解:www.codenong.com/js91edeffca…
influxdb原理那些事:luoxn28.github.io/2020/01/28/… www.linuxdaxue.com/influxdb-pr…
一份比较详细的文档:jasper-zhang1.gitbooks.io/influxdb/co…
官方文档:docs.influxdata.com/influxdb/v1…
时间序列数据的存储和计算 - 开源时序数据库解析(三):zhuanlan.zhihu.com/p/32710333