欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

谈谈InfluxDB存储原理

程序员文章站 2022-07-13 15:37:48
...

LSM Tree

LSM Tree (Log-Structured Merge Tree) 即日志合并树,被用于大量的数据库引擎中,如Hbase、LevelDB等。适用于海量数据的写入,而查询少的情况。主要思想是随机写转化为顺序写。基本流程为,最新的数据驻留在磁盘中,等到积累到足够多之后,再内存中有序的数据合并追加到磁盘队尾。为了解决防止数据丢失,使用WAL(Write Ahead Log)方式,写入内存的同时写入文件,用来恢复内存中的数据。以 LevelDB 为例,内存 中的数据达到指定阀值后会在写入一个新的文件。当某一层的文件数超过一定值后,就会将该层下的一个文件和更高一级的文件合并,由于 文件数据都是有序的,相当于是一个多路归并排序,所以合并操作相当快速,最终生成一个新的 文件,将旧的文件删除,这样就完成了一次合并过程。这也大概是为什么叫Level的的原因吧。

这里推荐几篇优秀的LSM Tree文章

1. LSM-Tree VS B-Tree

2. 野猪书读书笔记第三章

InfluxDB 存储架构

InfluxDB在经历了LSM Tree、B+Tree等几种尝试后,最终自研TSM,TSM全称是Time-Structured Merge Tree,思想类似LSM。我们先看它的整体架构:
谈谈InfluxDB存储原理

名词解释
  1. Shard
    上一篇文章中提到过这个概念,InfluxDB 中按照数据的时间戳所在的范围,会去创建不同的Shard Group,而Shard Group中会包含一个至多个Shard,单机版本中只有一个Shard。每一个 shard 都有自己的 cache、wal、tsm files 以及 compactor。
    2.WAL
    wal 文件其作用就是为了防止系统崩溃导致的数据丢失。WAL是一种写优化的存储格式,允许持久写入,但不易于查询
  2. Cache
    Cache是WAL中存储的数据的内存表示形式。达到一定阈值时与存储在TSM文件中的数据合并。
    内存中暂存数据的地方,其实是一个map,key 为 seriesKey + FiledName,value为entry,具体实现为List<fieldkey,values>,values根据时间来排序。插入数据时,同时往 cache 与 wal 中写入数据,当Cache中的数据达到25M(默认)全部写入 tsm 文件。
  3. TSM
  4. Compactor

文件目录介绍

Linux版本的数据文件位于/var/lib/influxdb/,Windows位于C:/用户/.influxd/下。主要有三个目录,分别是 meta, wal 以及 data 。meta 用于存储数据库的一些元数据,meta.db 文件。wal 目录存放预写日志文件,以 .wal 结尾。data 目录存放实际存储的数据文件,以 .tsm 结尾。整体结构如下所示:

influxdb/
  ├── data/
  |    └── [DB name]/
  |        └── [retention policy]/
  |        |    └── [shard group id]/
  |        |        ├── 000000001-0000000001.tsm
  |        |        └── fields.idx
  |        |		    └── index/
  |        |			      └── 0/
  |        |			          └── L0-000001.tsl 
  |        |			          └── MANIFEST
  |        |			      └── .../
  |        |			      └── 7/
  |        └── _series
  |            └──0/
  |              └──0000
  |            └──.../
  |            └──7/
  ├── meta/
  |    └── meta.db
  └── wal/
      └── [DB Name]/
          └── [retention policy]/ 
              └── [shard group id]/ 
                  └── _00001.wal

WAL

新的Ponit到来时,首先将被序列化,使用Snappy压缩并通过fsync写入WAL文件,然后在加入到内存中。一个WAL文件被称为一个 segment。写入文件的格式基于TLV(Type-length-value)标准。其中第一个字节代表条目的类型(写或删除),一个4字节uint32代表压缩块的长度,然后是压缩块。文件大小达到10M,则关闭才文件并开一个文件。当Cache中的数据写入TSM文件中后会删除对应的WAL文件。

Series File

_series目录保存了所有当前库下所有的Series key与Series Id的映射关系, id <-> series key数据基于id求余分区,分到8个目录中,在分区目录中在进行分文件存储,即超过256M就新开一个文件,防止文件过大。分区中的每个文件又称为Series segment。下面看segment的存储格式

+--------+---------+-------+-------+------+
| Magic  | version | Entry | Entry | ...  |  
| 4 bytes| 1 bytes |   N   |   N   | ...  |  
+--------+---------+-------+-------+------+

Magic占4byte,固定值为SSEG,意思就是Series segment,主要看Entry结构。

+------------------------------------------------------------------------------------+
|                                   Series segment                                   |
+---------+----------+---------+-----------------+-------------+----------+----------+
|  Flag   │ SeriesId |  Size   │ MeasurementSize | Measurement | TagCount |   Tags   |
| 1 bytes │  8 byte  | N bytes │      2 byte     |    N byte   |  N byte  |  N byte  |
+--------------------+---------------------------+-------------+----------+----------+

Flag标记Series key是新加还是删除,SeriesId对应一个int64。Size表示Series key的长度,TagCount表示tag的数量。这里用了varint变长编码,Series key的长度基本上会小于127,因此这种编码压缩方式基本上只会占用一个字节,TagCount同理。Tags是由多个Tag组成,具体格式如下。

+-------------------------------------------------------+
|                     Tag                               |
+----------------+---------+----------------+-----------+
|  Tag key size  │ tag key | Tag value size │ tag value |
|    2 bytes     │  N byte |    2 bytes     │   N byte  |
+----------------+---------+----------------+-----------+

这里笔者有一个疑问,为何 Tag key size 和 Tag value size 没有使用varint方式编码?

TSI File

TSM File

Cache中的数据会不间断的写入TMS file,一个TSM文件由四个部分组成:head,block,index和Footer。

+--------+------------------------------------+-------------+--------------+
| Header |               Blocks               |    Index    |    Footer    |
|5 bytes |              N bytes               |   N bytes   |   4 bytes    |
+--------+------------------------------------+-------------+--------------+

Head用于标识文件类型和版本号。

+-------------------+
|      Header       |
+-------------------+
|  Magic  │ Version |
| 4 bytes │ 1 byte  |
+-------------------+
  • Magic: 用于区分是哪一个存储引擎,目前使用的 tsm1 引擎,MagicNumber 为 0x16D116D1。
  • Version (1 byte): 目前是 tsm1 引擎,此值固定为 1

Blocks 内部是一些连续的 Block,block 是 InfluxDB 中的最小读取对象,每次读取操作都会读取一个 block。每一个 Block 分为 CRC32 值和 Data 两部分,CRC32 值用于校验 Data 的内容是否有问题。Data 的长度记录在之后的 Index 部分中。

+--------------------------------------------------------------------+
│                           Blocks                                   │
+---------------------+-----------------------+----------------------+
|       Block 1       |        Block 2        |       Block N        |
+---------------------+-----------------------+----------------------+
|   CRC    |  Data    |    CRC    |   Data    |   CRC    |   Data    |
| 4 bytes  | N bytes  |  4 bytes  | N bytes   | 4 bytes  |  N bytes  |
+---------------------+-----------------------+----------------------+

每个block内存储的是某个TimeSeries的一段时间范围内的值,即某个时间段下measurement+tag set+field的值,Block内部会根据field的不同的值的类型采取不同的压缩策略,以达到最优的压缩效率。Block具体格式如下

+--------------------------------------------------------------------+
│                           Block                                    │
+-------------+----------------+----------------+------------+-------+
| 	  CRC     | FieldValueType | TimeStamp Size | TimeStamps | values|
|   4 bytes   |      1 bytes   |       -     | 			-    |    -	 |
+-------------+----------------+----------------+------------+-------+
  • FieldValueType: 表示该DataBlock存储的FieldValue类型,InfluxDB中存在5中FieldValueType: Float, Integer, Unsigned, Boolean, String
  • TimestampSize: 表示TimeStamps block的长度,使用可变长编码
  • Timestamps: 将时间按列使用delta-delta编码压缩
  • Values: 将FieldValue值按列进行压缩,不同类型的FieldValue使用不同的压缩算法

check block ?

Index是对Blocks的索引,首先按键顺序排列,然后按时间排序。key就是Series key + 分隔符 + Field name。每个Point有多个字段会在TSM文件中创建多个索引条目。

+-----------------------------------------------------------------------------+
│                                   Index                                     │
+-----------------------------------------------------------------------------+
│ Key Len │   Key   │ Type │ Count │Min Time │Max Time │ Offset │  Size  │....│
│ 2 bytes │ N bytes │1 byte│2 bytes│ 8 bytes │ 8 bytes │8 bytes │4 bytes │    │
+-----------------------------------------------------------------------------+
  • **Key Len **: key 的长度。
  • **Key **: 这里的 key 指的是 Series Key + 分隔符 + fieldName。
  • Type: Field Name 所对应的 FieldValue 的类型,也就是 Block 中 Data 内的数据的类型。
  • Count: 后面 Blocks 索引的个数。
  • Min Time : block 中 最小时间戳。
  • Max Time : block 中最大时间戳。
  • **Offset **: block 在整个 TSM File 中的偏移量。
  • **Size **: block 的大小。根据 Offset + Size 字段就可以快速读取出一个 block 中的内容。

Footer用于存储索引起点的偏移量,方便将索引信息加载到内存中。

+---------+
│ Footer  │
+---------+
│Index Ofs│
│ 8 bytes │
+---------+

//TODO 间接索引?

InfluxDB存储总结

相关标签: 时序数据库