时序数据库连载系列: 时序数据库一哥InfluxDB之存储机制解析

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云数据库 Tair(兼容Redis),内存型 2GB
简介: InfluxDB 的存储机制解析 本文介绍了InfluxDB对于时序数据的存储/索引的设计。由于InfluxDB的集群版已在0.12版就不再开源,因此如无特殊说明,本文的介绍对象都是指 InfluxDB 单机版 1. InfluxDB 的存储引擎演进 尽管InfluxDB自发布以来历时三年多,其存储引擎的技术架构已经做过几次重大的改动, 以下将简要介绍一下InfluxDB的存储引擎演进的过程。

InfluxDB 的存储机制解析

本文介绍了InfluxDB对于时序数据的存储/索引的设计。由于InfluxDB的集群版已在0.12版就不再开源,因此如无特殊说明,本文的介绍对象都是指 InfluxDB 单机版

1. InfluxDB 的存储引擎演进

尽管InfluxDB自发布以来历时三年多,其存储引擎的技术架构已经做过几次重大的改动, 以下将简要介绍一下InfluxDB的存储引擎演进的过程。

1.1 演进简史

  • 版本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 方案**
    

1.2 演进的考量

InfluxDB的存储引擎先后尝试过包括LevelDB, BoltDB在内的多种方案。但是对于InfluxDB的下述诉求终不能完美地支持:

  • 时序数据在降采样后会存在大批量的数据删除

    => *LevelDB的LSMTree删除代价过高*
  • 单机环境存放大量数据时不能占用过多文件句柄

    => *LevelDB会随着时间增长产生大量小文件*
  • 数据存储需要热备份

    => *LevelDB只能冷备*
  • 大数据场景下写吞吐量要跟得上

    => *BoltDB的B+tree写操作吞吐量成瓶颈*
  • 存储需具备良好的压缩性能

    => *BoltDB不支持压缩*
    

此外,出于技术栈的一致性以及部署的简易性考虑(面向容器部署),InfluxDB团队希望存储引擎 与 其上层的TSDB引擎一样都是用GO编写,因此潜在的RocksDB选项被排除

基于上述痛点,InfluxDB团队决定自己做一个存储引擎的实现。

2 InfluxDB的数据模型

在解析InfluxDB的存储引擎之前,先回顾一下InfluxDB中的数据模型。

在InfluxDB中,时序数据支持多值模型,它的一条典型的时间点数据如下所示:

图 1
030

  • measurement:

    指标对象,也即一个数据源对象。每个measurement可以拥有一个或多个指标值,也即下文所述的**field**。在实际运用中,可以把一个现实中被检测的对象(如:“cpu”)定义为一个measurement
  • tags:

    概念等同于大多数时序数据库中的tags, 通常通过tags可以唯一标示数据源。每个tag的key和value必须都是字符串。
  • field:

    数据源记录的具体指标值。每一种指标被称作一个“field”,指标值就是 “field”对应的“value”
  • timestamp:

    数据的时间戳。在InfluxDB中,理论上时间戳可以精确到 **纳秒**(ns)级别
    

此外,在InfluxDB中,measurement的概念之上还有一个对标传统DBMS的 Database 的概念,逻辑上每个Database下面可以有多个measurement。在单机版的InfluxDB实现中,每个Database实际对应了一个文件系统的 目录

2.1 Serieskey的概念

InfluxDB中的SeriesKey的概念就是通常在时序数据库领域被称为 时间线 的概念, 一个SeriesKey在内存中的表示即为下述字符串(逗号和空格被转义)的 字节数组(github.com/influxdata/influxdb/model#MakeKey())

{measurement名}{tagK1}={tagV1},{tagK2}={tagV2},...

其中,SeriesKey的长度不能超过 65535 字节

2.2 支持的Field类型

InfluxDB的Field值支持以下数据类型:

Datatype Size in Mem Value Range
Float 8 bytes 1.797693134862315708145274237317043567981e+308 ~ 4.940656458412465441765687928682213723651e-324
Integer 8 bytes -9223372036854775808 ~ 9223372036854775807
String 0~64KB String with length less than 64KB
Boolean 1 byte true 或 false

在InfluxDB中,Field的数据类型在以下范围内必须保持不变,否则写数据时会报错 类型冲突

同一Serieskey + 同一field + 同一shard

2.3 Shard的概念

在InfluxDB中, 能且只能 对一个Database指定一个 Retention Policy (简称:RP)。通过RP可以对指定的Database中保存的时序数据的留存时间(duration)进行设置。而 Shard 的概念就是由duration衍生而来。一旦一个Database的duration确定后, 那么在该Database的时序数据将会在这个duration范围内进一步按时间进行分片从而时数据分成以一个一个的shard为单位进行保存。

shard分片的时间 与 duration之间的关系如下

Duration of RP Shard Duration
< 2 Hours 1 Hour
>= 2 Hours 且 <= 6 Months 1 Day
> 6 Months 7 Days

新建的Database在未显式指定RC的情况下,默认的RC为 数据的Duration为永久,Shard分片时间为7天

注: 在闭源的集群版Influxdb中,用户可以通过RC规则指定数据在基于时间分片的基础上再按SeriesKey为单位进行进一步分片

3. InfluxDB的存储引擎分析

时序数据库的存储引擎主要需满足以下三个主要场景的性能需求

  1. 大批量的时序数据写入的高性能
  2. 直接根据时间线(即Influxdb中的 Serieskey )在指定时间戳范围内扫描数据的高性能
  3. 间接通过measurement和部分tag查询指定时间戳范围内所有满足条件的时序数据的高性能

InfluxDB在结合了1.2所述考量的基础上推出了他们的解决方案,即下面要介绍的 WAL + TSMFile + TSIFile的方案

3.1 WAL解析

InfluxDB写入时序数据时为了确保数据完整性和可用性,与大部分数据库产品一样,都是会先写WAL,再写入缓存,最后刷盘。对于InfluxDB而言,写入时序数据的主要流程如同下图所示:

图 2
031_jpeg

InfluxDB对于时间线数据和时序数据本身分开,分别写入不同的WAL中,其结构如下所示:

索引数据的WAL

由于InfluxDB支持对Measurement,TagKey,TagValue的删除操作,当然随着时序数据的不断写入,自然也包括 增加新的时间线,因此索引数据的WAL会区分当前所做的操作具体是什么,它的WAL的结构如下图所示

图 3
032

时序数据的WAL

由于InfluxDB对于时序数据的写操作永远只有单纯写入,因此它的Entry不需要区分操作种类,直接记录写入的数据即可

图 4
033

3.2 TSMFile解析

TSMFile是InfluxDB对于时序数据的存储方案。在文件系统层面,每一个TSMFile对应了一个 Shard

TSMFile的存储结构如下图所示:

图 5
035

其特点是在一个TSMFile中将 时序数据(i.e Timestamp + Field value)保存在数据区;将Serieskey 和 Field Name的信息保存在索引区,通过一个基于 Serieskey + Fieldkey构建的形似B+tree的文件内索引快速定位时序数据所在的 数据块

注: 在当前版本中,单个TSMFile的最大长度为2GB,超过时即使是同一个Shard,也会继续新开一个TSMFile保存数据。本文的介绍出于简单化考虑,以下内容不考虑同一个Shard的TSMFile分裂的场景

  • 索引块的构成

    上文的索引块的构成,如下所示:
    
    *图 6*

    036

其中 **索引条目** 在InfluxDB的源码中被称为`directIndex`。在TSMFile中,索引块是按照 Serieskey + Fieldkey **排序** 后组织在一起的。

明白了TSMFile的索引区的构成,就可以很自然地理解InfluxDB如何高性能地在TSMFile扫描时序数据了:

1. 根据用户指定的时间线(Serieskey)以及Field名 在 **索引区** 利用二分查找找到指定的Serieskey+FieldKey所处的 **索引数据块**
2. 根据用户指定的时间戳范围在 **索引数据块** 中查找数据落在哪个(*或哪几个*)**索引条目**
3. 将找到的 **索引条目** 对应的 **时序数据块** 加载到内存中进行进一步的Scan

*注:上述的1,2,3只是简单化地介绍了查询机制,实际的实现中还有类似扫描的时间范围跨索引块等一系列复杂场景*

<br>
  • 时序数据的存储

    图 2中介绍了时序数据块的结构:即同一个 Serieskey + Fieldkey 的 所有时间戳 - Field值对被拆分开,分成两个区:Timestamps区和Value区分别进行存储。它的目的是:实际存储时可以分别对时间戳和Field值按不同的压缩算法进行存储以减少时序数据块的大小

    采用的压缩算法如下所示:

    • Timestamp: Delta-of-delta encoding
    • Field Value:由于单个数据块的Field Value必然数据类型相同,因此可以集中按数据类型采用不同的压缩算法

    做查询时,当利用TSMFile的索引找到文件中的时序数据块时,将数据块载入内存并对Timestamp以及Field Value进行解压缩后以便继续后续的查询操作。

3.3 TSIFile解析

有了TSMFile,第3章开头所说的三个主要场景中的场景1和场景2都可以得到很好的解决。但是如果查询时用户并没有按预期按照Serieskey来指定查询条件,而是指定了更加复杂的条件,该如何确保它的查询性能?通常情况下,这个问题的解决方案是依赖倒排索引(Inverted Index)。

InfluxDB的倒排索引依赖于下述两个数据结构

  • map<SeriesID, SeriesKey>
  • map<tagkey, map<tagvalue, List<SeriesID>>>

它们在内存中展现如下:

图 7
037

图 8
038

但是在实际生产环境中,由于用户的时间线规模会变得很大,因此会造成倒排索引使用的内存过多,所以后来InfluxDB又引入了 TSIFile

TSIFile的整体存储机制与TSMFile相似,也是以 Shard 为单位生成一个TSIFile。具体的存储格式就在此不赘述了。

4. 总结

以上就是对InfluxDB的存储机制的粗浅解析,由于目前所见的只有单机版的InfluxDB,所以尚不知道集群版的InfluxDB在存储方面有哪些不同。但是,即便是这单机版的存储机制,也对我们设计时序数据库有着重要的参考意义。

阿里云时序时空数据库TSDB 1元购!立即体验:https://promotion.aliyun.com/ntms/act/tsdbtry.html?spm=5176.149792.775960.1.dd9e34e2zgsuEM&wh_ttid=pc

目录
相关文章
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
27天前
|
数据库 索引
深入探索数据库索引技术:回表与索引下推解析
【10月更文挑战第15天】在数据库查询优化的领域中,回表和索引下推是两个核心概念,它们对于提高查询性能至关重要。本文将详细解释这两个术语,并探讨它们在数据库操作中的作用和影响。
46 3
|
1月前
|
存储 缓存 算法
分布式锁服务深度解析:以Apache Flink的Checkpointing机制为例
【10月更文挑战第7天】在分布式系统中,多个进程或节点可能需要同时访问和操作共享资源。为了确保数据的一致性和系统的稳定性,我们需要一种机制来协调这些进程或节点的访问,避免并发冲突和竞态条件。分布式锁服务正是为此而生的一种解决方案。它通过在网络环境中实现锁机制,确保同一时间只有一个进程或节点能够访问和操作共享资源。
73 3
|
1月前
|
存储 NoSQL 关系型数据库
数据库技术深度解析:从基础到进阶
【10月更文挑战第17天】数据库技术深度解析:从基础到进阶
59 0
|
1月前
|
SQL 关系型数据库 MySQL
数据库导入SQL文件:全面解析与操作指南
在数据库管理中,将SQL文件导入数据库是一个常见且重要的操作。无论是迁移数据、恢复备份,还是测试和开发环境搭建,掌握如何正确导入SQL文件都至关重要。本文将详细介绍数据库导入SQL文件的全过程,包括准备工作、操作步骤以及常见问题解决方案,旨在为数据库管理员和开发者提供全面的操作指南。一、准备工作在导
273 0
|
26天前
|
存储 负载均衡 监控
数据库多实例的深入解析
【10月更文挑战第24天】数据库多实例是一种重要的数据库架构方式,它为数据库的高效运行和灵活管理提供了多种优势。在实际应用中,需要根据具体的业务需求和技术环境,合理选择和配置多实例,以充分发挥其优势,提高数据库系统的性能和可靠性。随着技术的不断发展和进步,数据库多实例技术也将不断完善和创新,为数据库管理带来更多的可能性和便利。
93 57
|
13天前
|
存储 消息中间件 算法
深入探索操作系统的心脏——内核机制解析
本文旨在揭示操作系统核心——内核的工作原理,通过剖析其关键组件与机制,为读者提供一个清晰的内核结构图景。不同于常规摘要的概述性内容,本文摘要将直接聚焦于内核的核心概念、主要功能以及其在系统管理中扮演的角色,旨在激发读者对操作系统深层次运作原理的兴趣与理解。
|
25天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
24 3
|
1月前
|
Java 开发者 UED
Java编程中的异常处理机制解析
在Java的世界里,异常处理是确保程序稳定性和可靠性的关键。本文将深入探讨Java的异常处理机制,包括异常的类型、如何捕获和处理异常以及自定义异常的创建和使用。通过理解这些概念,开发者可以编写更加健壮和易于维护的代码。
|
1月前
|
Web App开发 SQL 数据库
使用 Python 解析火狐浏览器的 SQLite3 数据库
本文介绍如何使用 Python 解析火狐浏览器的 SQLite3 数据库,包括书签、历史记录和下载记录等。通过安装 Python 和 SQLite3,定位火狐数据库文件路径,编写 Python 脚本连接数据库并执行 SQL 查询,最终输出最近访问的网站历史记录。

相关产品

  • 云原生多模数据库 Lindorm
  • 推荐镜像

    更多
    下一篇
    无影云桌面