27.2k star,开源时序数据库里全球排名第一,InfluxDB何以做到?
wptr33 2025-05-21 16:55 19 浏览
一 什么是时序数据库
时序数据库,全称时间序列数据库(Time Series Database,TSDB),用于存储大量基于时间的数据,时序数据(Time Series Data)指的是一系列基于时间的数据,例如 CPU 利用率,北京的房价变化趋势,某一地区的温度变化等。
时序数据库支持时序数据的快速写入、持久化,多维度查询、聚合等操作,同时可以记录所有的历史数据,查询时将时间作为数据的过滤条件。
二 初识InfluxDB
InfluxDB 是时序数据库中应用比较广泛的一种,在 DB-Engines TSDB rank 中位居首位,可见 InfluxDB 在互联网的受欢迎程度是非常高的。
三 InfluxDB 的存储引擎演进
- 版本0.9.0之前
**基于 LevelDB的LSMTree方案** - 版本0.9.0~0.9.4
**基于BoltDB的mmap COW B+tree方案** - 版本0.9.5~1.2
**基于自研的 WAL + TSMFile 方案**(TSMFile方案是0.9.6版本正式启用,0.9.5只是提供了原型) - 版本1.3~至今
**基于自研的 WAL + TSMFile + TSIFile 方案**
InfluxDB的存储引擎先后尝试过包括LevelDB, BoltDB在内的多种方案。但是对于InfluxDB的下述诉求终不能完美地支持:
- 时序数据在降采样后会存在大批量的数据删除 => *LevelDB的LSMTree删除代价过高*
- 单机环境存放大量数据时不能占用过多文件句柄 => *LevelDB会随着时间增长产生大量小文件*
- 数据存储需要热备份 => *LevelDB只能冷备*
- 大数据场景下写吞吐量要跟得上 => *BoltDB的B+tree写操作吞吐量成瓶颈*
- 存储需具备良好的压缩性能 => *BoltDB不支持压缩*
此外,出于技术栈的一致性以及部署的简易性考虑(面向容器部署),InfluxDB团队希望存储引擎与其上层的TSDB引擎一样都是用GO编写,因此潜在的RocksDB选项被排除
基于上述痛点,InfluxDB团队决定自己做一个存储引擎的实现。
四 InfluxDB的使用场景
时序数据的使用场景广泛,包括 DevOps 监控,应用程序指标,IoT 传感器数据,实时动态数据分析等场景。
InfluxDB是一种时序数据库,时序数据库常用于监控场景,比如运维和IOT(物联网)领域,此类数据库旨在存储时序数据和实时计算。
如:将服务器上的CPU的使用情况每隔5秒向InfluxDB中写入一条数据,在图形界面中写一个查询过去每10分钟CPU的平均使用情况,再将该查询开发一个定时任务,每10秒钟执行一次并配置一条规则:查询执行结果 > xxx,就立即触发告警。
上述就是一个监控指标的场景,在IOT领域中也有大量的指标需要监控,比如:机械设备的传感器频率、农田湿度温度等等。
时序数据库相对关系型数据库而言,时序数据库专注的是写入性能,时序数据库不关心事务、不关注更新操作(只写不改)。通常指标数据都是关心最近某段时间的数据,之后的数据基本不会再用。时序数据库的设计特点是冷热差别明显,对最近的数据时序数据库会优先加载到内存中。
五 InfluxDB的组件
时序数据库一般用于在监控场景,大体上,数据的应用可以分四部分:数据采集、存储、查询&聚合及告警
对应InfluxDB 1.x开始也推出了TICK生态全套的解决方案:
- T:Telegraf 数据采集组件
- I:InfluxDB 数据存储组件
- C:Chronograf 用户UI数据管理功能
- K:Kapacitor 后台处理报警信息
在InfluxDB 2.x后已经把C、K合并到了I中了,这是两个版本在生态上的差别,在2.x上易用性也随之提高了一个等级:我们只需要安装InfluxDB就得到一个管理UI界面并且附带了定时任务和告警功能。
T是一个单独的插件,专注于收集各种外部中间件的数据写入到InfluxDB中,这个需要单独研究该插件的使用。
安装下载地址:
https://portal.influxdata.com/downloads/
下面是在web界面中新增Telegraf的配置文件,它已经给支持了非常多的中间件产品,就按照它的示例去给Telegraf机器上设置环境变量和启动即可使用。
时序数据库与我们熟悉的关系型数据库有所不同,首先需要了解一下 InfluxDB 中字段的含义,如下图所示:
六 InfluxDB的特点
● 数据写入:
①.高并发高吞吐,可持续的数据写入。
②.写多读少,时序数据 95% 以上都是写操作,例如在监控系统数据的时候,监控数据特别多,但是通常只会关注几个关键指标。
③.数据实时写入,不支持数据更新,但是可以人为更新修改。
● 数据分析与查询:
①.数据查询是按照时间段读取,例如 1 小时,1 分钟,给出具体时间范围。
②.最近的数据读取率高,越旧的数据读取率越低。
③.多种精度查询和多种维度分析。
● 数据存储:
①.存储数据规模大的数据,监控数据的数据大多数情况下都是 TB 或者 PB 级。
②.数据存放具有时效性,InfluxDB 提供了保存策略,可以认为是数据的保质期,超过周期范围,就可以认为数据失效,需要回收。节约存储成本,清理低价值的数据。
七 InfluxDB数据结构
Telegraf的内部数据结构叫做InfluxDB行协议。如下图所示:
Telegraf本身是InfluxData公司专门为InfluxDB开发的数据采集器。上面这种数据格式是InfluxDB数据库使用的,只要数据符合上面这种格式,就能通过InfluxDB的API将数据导入数据库。所以,自家的插件当然支持自家的生态了,InfluxDB。
与 CSV 相似,在 InfluxDB 行协议中,一条数据和另一条数据之间使用换行符分隔, 所以一行就是一条数据。另外,在时序数据库领域,一行数据一行数据由下面 4 种元素构成。
- measurement(测量名称)
- Tag Set(标签集)
- Field Set(字段集)
- Timestamp(时间戳)
协议中的数据类型及其格式
- Float(浮点数):IEEE-754标准的64位浮点数。这是默认的数据类型。
- Integer(整数):有符号64位整数。需要在数字的尾部加上一个小写数字 i 。
- UInteger(无符号整数):无符号64位整数。需要在数字的尾部加上一个小写数字 u 。
- String(字符串):普通文本字符串,长度不能超过64KB
- Boolean(布尔值):true或者false
- Unix Timestamp(Unix 时间戳):myMeasurementName fieldKey="fieldValue"1556813561098000000
- 注释:以井号 # 开头的一行会被当做注释。
基本数据结构
points的数据结构介绍
八 InfluxDB存储原理
InfluxDB 的存储结构树是时间结构合并树(Time-Structured Merge Tree,TSM),它是由日志结构化合并树(Log-Structured Merge Tree,LSM),根据实际需求变化而来的。
①.LSM 树
LSM 树包含三部分:Memtable,Immutable 和 SSTable。MemTable 是内存中的数据结构,用于保存最近产生的数据,并按照 Key 有序地组织数据。内存并不是可靠存储,若断电就会丢失数据,因此通常会使用预写式日志 (Write-ahead logging,WAL) 的方式来保证数据的可靠性。
②.TSM 存储引擎
TSM 存储引擎主要包括四部分:Cache,WAL,TSM File,Compactor。下图中 shard 与 TSM 引擎主要部分放在一起,但其实 shard 在是 TSM 存储引擎之上的一个概念。在 InfluxDB 中按照数据产生的时间范围,会创建不同的 shard 分组,每个 shard 都有本身的 cache、wal、tsm file 以及 compactor。
InfluxDB文件目录结构
九 InfluxDB的写入
InfluxDB写入时序数据时为了确保数据完整性和可用性,与大部分数据库产品一样,都是会先写WAL,再写入缓存,最后刷盘。对于InfluxDB而言,写入时序数据的主要流程如同下图所示:
InfluxDB提供了多种接口协议供外部应用写入,比如可以使用collected采集数据上传,可以使用opentsdb作为输入,也可以使用http协议以及udp协议批量写入数据。批量数据进入到InfluxDB之后总体会经过三个步骤的处理,如下图所示:
- 批量时序数据shard路由:InfluxDB首先会将这些数据根据shard的不同分成不同的分组,每个分组的时序数据会发送到对应的shard。每个shard相当于HBase中region的概念,是InfluxDB中处理用户读写请求的单机引擎。
- 倒排索引引擎构建倒排索引:InfluxDB中shard由两个LSM引擎构成 – 倒排索引引擎和TSM引擎。时序数据首先会经过倒排索引引擎构建倒排索引,倒排索引用来实现InfluxDB的多维查询。
- TSM引擎持久化时序数据:倒排索引构建成功之后时序数据会进入TSM Engine处理。TMS Engine处理流程和通用LSM Engine基本一样,先将写入请求追加写入WAL日志,再写入cache,一旦满足特定条件会将cache中的时序数据执行flush操作落盘形成TSM File。
十 InfluxDB的读取
InfluxDB支持类SQL查询,称为InfluxQL。InfluxQL支持基本的DDL操作和DML操作语句,详见InfluxQL_Spec,比如Select语句:
select_stmt = "SELECT" fields from_clause [ into_clause ] [ where_clause ]
[ group_by_clause ] [ order_by_clause ] [ limit_clause ]
[ offset_clause ] [ slimit_clause ] [ soffset_clause ] .
读取流程大体如下:
整个读取流程从宏观上分为四个部分:
1. Query:InfluxQL允许用户使用类SQL语句执行查询分析聚合,InfluxQL语法详见:
https://docs.influxdata.com/influxdb/v1.0/query_language/spec/
2. QueryParser:InfluxQL进入系统之后,系统首先会对InfluxQL执行切词并解析为抽象语法树(AST),抽象树中标示出了数据源、查询条件、查询列以及聚合函数等等,分别对应上图中Source、Condition以及Aggration。
3. BuildIterators:InfluxQL语句转换为Query实体对象之后,就进入读取流程中最重要最核心的一个环节 – 构建Iterator体系。构建Iterator体系是一个非常复杂的逻辑过程,其中细节非常繁复,笔者尽可能化繁为简,将其中的主线抽出来。为了方便理解,笔者将Iterator体系分为三个子体系:顶层Iterator子体系、中间层Iterator子体系以及底层Iterator子体系。
4. Emitter.Emit:Iterator体系构建完成之后就完成了查询聚合前的准备工作,接下来就开始干活了。干活逻辑简单来讲是遍历所有FieldIterator,对每个FieldIterator执行一次Next函数,就会返回每个查询列的结果值,组装到一起就是一行数据。FieldIterator执行Next()函数会传递到最底层的TagsetIterator,TagsetIterator执行Next函数实际返回真实的时序数据。
TSDB存储引擎(实际上就是一个Shard)根据用户的查询请求执行原始数据的查询就是上文中提到的底层Iterator子体系的构建。查询过程分为两个部分:倒排索引查询过滤以及TSM数据层查询,前者通过Query中的where条件结合倒排索引过滤掉不满足条件的SeriesKey;后者根据留下的SeriesKey以及where条件中时间段信息(TimeRange)在TSMFile中以及内存中查出最终满足条件的数值列。TSDB存储引擎会将查询到的所有满足条件的原始数值列返回给上层,上层根据聚合函数对原始数据进行聚合并将聚合结果返回给用户。整个过程如下图所示:
上图需要从底部向上浏览,整个流程可以整理为如下:
1. 根据where condition以及所有倒排索引文件查处所有满足条件的SeriesKey
2. 将满足条件的SeriesKey根据GroupBy维度列进行分组,不同分组后续的所有操作都可以独立并发执行,因此可以多线程处理
3. 针对某个分组的SeriesKey集合以及待查询列,根据指定查询时间段(TimeRange)在所有TSMFile中根据B+树索引构建查询iterator
4. 将满足条件的原始数据返回给上层进行聚合运算,并将聚合运算的结果返回给用户
实际执行的过程可能比较抽象,为了更好的理解,笔者在下半部分举了一个示例。没有理解上面的逻辑没关系,可以先看下面的示例,看完之后再看上面的理论逻辑相信会更加容易理解。
十一 InfluxDB的删除
一般LSM引擎处理删除通常都采用Tag标记的方式,即删除操作和写入操作流程基本一致,只是数据上会多一个Tag标记 – deleted,表示该值已经被deleted。这种处理方案可以最小化删除代价,但万物有得必有失,减小了写入代价必然会增加读取代价,Tag标签方案在读取的时候需要对标记有deleted的数值进行特殊处理,这个代价还是很大的。HBase中删除操作就是采用Tag标记方案。
InfluxDB比较奇葩,对于删除操作处理的比较异类,通常InfluxDB不会删除一条记录,而是会删除某段时间内或者某个维度下的所有记录,甚至一张表的所有记录,这和通常的数据库有所不同。比如:
DROP SERIES FROM h2o_feet WHERE location = ‘santa_monica'
DELETE FROM "cpu" DELETE FROM "cpu" WHERE time < '2000-01-01T00:00:00Z' DELETE WHERE time < '2000-01-01T00:00:00Z'
InfluxDB中一个shard有两个LSM引擎,一个是倒排索引引擎(存储维度列到SeriesKey的映射关系,方便多维查找),一个是TSM Engine,用来存储实际的时序数据。如果是删除一条记录,通常只需要TSM Engine执行删除就可以,倒排索引引擎是不需要执行删除的。而如果是Drop Measurement这样的操作,那么两个LSM引擎都需要执行相应的删除。问题是,这两个引擎的删除策略完全不同,TSM Engine采用了一种同步删除策略,Inverted Index Engine采用了标记删除策略。如下图所示:
1. TSM Engine同步删除策略,整个删除流程可以分为如下四步:
(1)删除所有TSM File中满足条件的series,系统会遍历当前shard中所有TSM File,检查该File中是否存在满足删除条件的File,如果有会执行如下两个操作:
- TSM File Index相关处理:在内存中删除满足条件的Index Entry,通常删除会带有Time Range以及Key Range,而且TSM File Index会在引擎启动之后加载到内存。因此删除操作会将满足条件的Index Entry从内存中删除。
- 生成tombstoner文件:tombstoner文件会记录当前TSM File中所有被删除的时序数据,时序数据用[key, min, max]三个字段表示,其中key即SeriesKey+FieldKey,[min, max]表示要删除的时间段。如下图所示:
(2)删除Cache中满足条件的series
(3)在WAL中生成一条删除series的记录并持久化到硬盘
2. Inverted Index Engine 标记Tag删除策略,标记Tag删除非常简单,和一次写入流程基本相同:
(1)在WAL中生成一条flag为deleted的LogEntry并持久化到硬盘
(2)将要删除的维度信息写入Cache,需要标记deleted(设置type=deleted)
(3)当WAL大小超过阈值之后标记为deleted的维度信息会随Cache Flush到倒排索引文件
(4)和HBase一样,Inverted Index Engine中索引信息真正被删除发生在compact阶段
十二 参考资料
[1] InfluxDB文件结构解析:
https://blog.csdn.net/u012794915/article/details/100061367
[2] InfluxDB入门操作:
https://blog.csdn.net/qq_44766883/article/details/131511821
[3] InfluxDB 的存储机制解析:
https://zhuanlan.zhihu.com/p/604131607
[4] InfluxDB TSM存储引擎之数据读取:
http://hbasefly.com/2018/05/02/timeseries-database-7/
[5] InfluxDB TSM存储引擎之数据写入:
http://hbasefly.com/2018/03/27/timeseries-database-6/
[6] InfluxDB源码:
https://github.com/influxdata/influxdb
[7] InfluxDB 2.x概述和数据存储原理:
https://www.caodegao.com/archives/influxdb2x-gai-shu-he-shu-ju-yuan-li
[8] influxdb集成:
https://www.influxdata.com/products/integrations/
相关推荐
- MySQL进阶五之自动读写分离mysql-proxy
-
自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...
- 3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?
-
引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...
- 一文由浅入深带你完全掌握MySQL的锁机制原理与应用
-
本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...
- 验证Mysql中联合索引的最左匹配原则
-
后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...
- MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)
-
目录1.索引基础...
- 你会看 MySQL 的执行计划(EXPLAIN)吗?
-
SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...
- MySQL 从入门到精通(四)之索引结构
-
索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...
- mysql总结——面试中最常问到的知识点
-
mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...
- mysql总结——面试中最常问到的知识点(2)
-
首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...
- MySQL基础全知全解!超详细无废话!轻松上手~
-
本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...
- 深入剖析 MySQL 中的锁机制原理_mysql 锁详解
-
在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...
- Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析
-
引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...
- MySQL基础篇:DQL数据查询操作_mysql 查
-
一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...
- MySql:索引的基本使用_mysql索引的使用和原理
-
一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
如何将AI助手接入微信(打开ai手机助手)
-
Java面试必考问题:什么是乐观锁与悲观锁
-
SparkSQL——DataFrame的创建与使用
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)