环境: CentOS6.5_x64

InfluxDB版本:1.1.0

InfluxDB存储引擎看起来很像一个LSM Tree,它包含预写日志和类似存储在LSM Tree中的SSTables只读数据。 TSM文件包含已经排好序而且经过压缩的序列化数据。

InfluxDB会为每个时间块创建一个分区。例如,如果你有一个没有时间限制的存储策略,会以7天为时间块来创建分区。 这些分区会映射到底层数据库存储引擎。 每个数据库会有自己的WAL文件和TSM文件。

LSM Tree

如果要让写性能最优,最佳的实现方式是以追加的模式写磁盘文件。

如果要优化读性能,常见的几种优化措施如下:

  • 二分查找

  将数据以key的方式排好序,然后存储在文件中,通过二分查找的方式查找数据。

  • Hash

  将数据经过hash后放入特定的位置,以后可以通过哈希值直接读取。

  • B+ Tree

  使用B+树来组织数据,将数据完全排序,读取时会非常快。 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。 所以对于数据库, 基本都是用B+树作为索引机制, 而没有用二叉树或他的变种红黑树的。

  • 使用额外的索引文件

  除了本身存储的数据外,另外对其单独建立索引以加速读取。

  

二分查找要求数据完全排序,显然会影响数据库的写入性能;

对于数据库而言,因为要考虑到range查询, 要使用树系列(二叉树, 红黑树, B树, B+树),所以hash索引不行;

B+树将数据完全排序,读取时很快,但是当要修改数据时,就需要将新入数据下面的数据重新排序,所以面对海量的随机更新,B+树的性能会明显下降;

而使用外部索引的话,写数据时需要维护维护索引,根本无法抵御随机更新操作;

InfluxDB采用的是LSM Tree(Log Structured Merge Trees)存储结构,这种数据组织方式被应用于多种数据库,如Big Table、HBase、Cassandra、LevelDB等。

其实现思路和以上四中措施不太相同,它将随机写转换为顺序写,尽量保持日志型数据库的写性能优势,并提供相对较好的读性能。

具体实现如下:

1、当有写操作(含insert、update)时,先把数据写入内存,内存中通过某种数据结构(如skiplist)保持key有序,

2、同时将数据追写到磁盘Log文件中,以备必要时恢复;

3、内存数据定时(或按固定大小)排序后刷到磁盘,存储到磁盘的数据文件不可写且有序;

4、定时合并文件,将小树合并为大树,消除冗余数据,减少文件数量,优化查询速度;

LSM Tree存储结构执行写操作时,只需更新内存,硬盘的那份数据只是简单的追加操作,所以能处理大量随机更新操作;

LSM Tree存储结构执行读操作时,先从内存数据开始访问,如果在内存中访问不到, 再顺序从一个个磁盘文件中查找,由于文件本身有序,并且定期的合并减少了磁盘文件个数,因而查找过程相对较快速。

由于时序数据库对写入的速度要求比较高,而读取一般是一段时间范围内的数据,使用LSM Tree算法非常合适。

LSM Tree 描述:

http://www.benstopford.com/2015/02/14/log-structured-merge-trees/
http://blog.fatedier.com/2016/06/15/learn-lsm-tree/

存储引擎模块

存储引擎将一些组件捆绑在一起并且提供一些额外的接口用来存储和查询序列化数据。

它由以下这些组件构成,每个组件扮演不同的角色:

  • 内存索引

  内存索引是分区的索引,主要用于快速访问measurements,tags和series。

  • 预写日志

  预写日志是write-optimized存储格式的数据,允许写入持久化,但不易查询,写入时执行附加操作。

文件前缀: _

文件扩展名 : .wal

文件名称看起来大概是这样: _000001.wal

文件名字中的数字是递增的,它是WAL段的索引。

当一个WAL段的大小大于10M时( DefaultSegmentSize = 10 * 1024 * 1024),它会关闭当前段并且创建一个新的段。

每个WAL段存储多个经压缩的写和删除的操作指令。

┌───────────────────────────────────────────────────────────┐
│ WALEntry │
├──────────────┬──────────┬──────────┬───┬─────────────┬────┤
│ WALEntryType │ Data Len │ Data │...│WALEntryType │... │
│ byte │ bytes │ N bytes │ │ byte │ │
└──────────────┴──────────┴──────────┴───┴─────────────┴────┘

WALEntryType : 用于标记数据操作类型(写入、删除、连续删除)

const (
WriteWALEntryType WalEntryType = 0x01
DeleteWALEntryType WalEntryType = 0x02
DeleteRangeWALEntryType WalEntryType = 0x03
)

Data len : 后面数据的长度

Data :经过snappy压缩后的数据

相关配置参数可以在文件中查看:

influxdb-1.1.0/tsdb/engine/tsm1/wal.go

WAL文件中的数据存储结构如下:

┌────────────────────────────────────────────────────────────────────┐
│ WriteWALEntry │
├──────┬─────────┬────────┬───────┬─────────┬─────────┬───┬──────┬───┤
│ Type │ Key Len │ Key │ Count │ Time │ Value │...│ Type │...│
│ byte│ bytes │ N bytes│ bytes│ bytes │ N bytes │ │ byte│ │
└──────┴─────────┴────────┴───────┴─────────┴─────────┴───┴──────┴───┘

字段描述如下:

Type(1 byte) : 表示value的类型(支持的类型有:浮点型,整型,布尔型,字符串型)

Key Len(2 bytes) : 指定紧跟其后的Key的长度

Key(N bytes) :key内容

Count(4 bytes) :后面跟的(Time + Value作为一个整体)数据的个数

Time(8 bytes) :单个value的时间戳

Value(N bytes) :需要存储的数据(支持的类型有:浮点型,整型,布尔型,字符串型)

当Value为字符串类型时,Value 由于两部分构成:字符串长度(4Bytes) + 字符串内容(长度由前面决定)

case *StringValue:
if curType != stringEntryType {
return nil, fmt.Errorf("incorrect value found in %T slice: %T", v[].Value(), vv)
}
binary.BigEndian.PutUint32(dst[n:n+], uint32(len(vv.value)))
n +=
n += copy(dst[n:], vv.value)
  • 缓存

  缓存是存储在WAL文件中的数据在内存中的一份拷贝,数据按key的方式组织,未压缩。

  系统重启时,缓存会通过读取磁盘的WAL文件恢复。

  • TSM文件

  TSM文件是Influxdb中最终存储数据的载体,整体结构如下:

┌────────┬────────────────────────────────────┬─────────────┬──────────────┐
│ Header │ Blocks │ Index │ Footer │
│ bytes │ N bytes │ N bytes │ bytes │
└────────┴────────────────────────────────────┴─────────────┴──────────────┘

由四部分组成: Header,Blocks,Index,Footer

Header用于标识文件类型及版本号,Blocks用于存储数据,Index为Blocks的索引信息,Footer用于标识Index在TSM文件中的偏移量,便于快速访问。

Header结构如下:

┌───────────────────┐
│ Header │
├─────────┬─────────┤
│ Magic │ Version │
│ bytes │ byte │
└─────────┴─────────┘

Magic用于标识存储引擎类别,Version用于记录版本号,在influxdb1.1版本中定义如下:

MagicNumber uint32 = 0x16D116D1

Version byte = 

Blocks结构如下:

┌───────────────────────────────────────────────────────────┐
│ Blocks │
├───────────────────┬───────────────────┬───────────────────┤
│ Block │ Block │ Block N │
├─────────┬─────────┼─────────┬─────────┼─────────┬─────────┤
│ CRC │ Data │ CRC │ Data │ CRC │ Data │
│ bytes │ N bytes │ bytes │ N bytes │ bytes │ N bytes │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

Blocks由多个Block构成,每个Block包含CRC32和Data两部分,其中CRC32用于校验Data的内容是否有问题, Data为存储的数据,Data的长度记录在之后的Index部分中。

Index结构如下:

┌────────────────────────────────────────────────────────────────────────────┐
│ Index │
├─────────┬─────────┬──────┬───────┬─────────┬─────────┬────────┬────────┬───┤
│ Key Len │ Key │ Type │ Count │Min Time │Max Time │ Offset │ Size │...│
│ bytes │ N bytes │ byte│ bytes│ bytes │ bytes │ bytes │ bytes │ │
└─────────┴─────────┴──────┴───────┴─────────┴─────────┴────────┴────────┴───┘

Kye Len (2 bytes) : 代表紧随其后的key的长度

Key (N bytes) :Key的内容

Type :数据类型

influxdb-1.1./tsdb/engine/tsm1/encoding.go

...

const (
// BlockFloat64 designates a block encodes float64 values
BlockFloat64 = byte() // BlockInteger designates a block encodes int64 values
BlockInteger = byte() // BlockBoolean designates a block encodes boolean values
BlockBoolean = byte() // BlockString designates a block encodes string values
BlockString = byte() // encodedBlockHeaderSize is the size of the header for an encoded block. There is one
// byte encoding the type of the block.
encodedBlockHeaderSize =
)

Count(2 bytes) : 后面跟的(Min Time + Max Time + Offset + Size作为一个整体)数据的个数

Min Time(8 bytes) : block中value的最小时间戳

Max Time(8 bytes) : block中value的最大时间戳

Offset(8 bytes) : 该block在tsm文件中的偏移量

Size(4Bytes) : block的大小

Footer结构如下:

┌─────────┐
│ Footer │
├─────────┤
│Index Ofs│
│ bytes │
└─────────┘

tsm文件示例:

 D1  D1  2C  A0    1C  E6 C9 EF
2E E4 3F E4 7A E1 AE 7B C3 F4
C7 AE 7A E1 A0 A6 0F 1C E6
C9 F4 AC 3F EF AE 7A E1 AE C3
FC 7A E1 AE 7A F0 5F 6C
6F 5F 6F 2C 6F 3D
2C 6F 6E 3D
2D 7E 6C
E6 C9 EF 2E E4 E6 C9 EF 2E
E4 E6
C9 F4 AC E6 C9 F4 AC

参考代码:

curl -i -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE mydb"
curl -i -XPOST 'http://localhost:8086/write?db=mydb' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'

Header部分 : 16 D1 16 D1 01

Blocks部分 :

               2C  A0    1C  E6 C9 EF
2E E4 3F E4 7A E1 AE 7B C3 F4
C7 AE 7A E1 A0 A6 0F 1C E6
C9 F4 AC 3F EF AE 7A E1 AE C3
FC 7A E1 AE 7A F0

Index部分 :

                                5F 6C
6F 5F 6F 2C 6F 3D
2C 6F 6E 3D
2D 7E 6C
E6 C9 EF 2E E4 E6 C9 EF 2E
E4 E6
C9 F4 AC E6 C9 F4 AC

00 34 : 后面的52个字符为key

key内容:

                                    5F 6C
6F 5F 6F 2C 6F 3D
2C 6F 6E 3D
2D 7E 6C

data类型:0x00

count : 00 02

后面有两个数据

第一个数据:

       E6 C9 EF  2E E4   E6 C9 EF  2E
E4

这个时间段(13 E6 C9 EF 89 2E E4 00 - 13 E6 C9 EF 89 2E E4)的数据在5( 00 00 00 00 00 00 00 00 05)这个地方存,占用34(00 00 00 22)个byte

第二个数据:

                                          E6
C9 F4 AC E6 C9 F4 AC

Footer部分 : 00 00 00 00 00 00 00 49

TSM存储设计可参考如下文件:

influxdb-1.1./tsdb/engine/tsm1/DESIGN.md
  • 文件存储器

  在替换和删除TSM文件时,它确保TSM文件创建的原子性。

  • 压缩规划器

  检测那个TSM文件可以压缩,并确保多个并发的压缩互不干扰。

  • 压缩器

  主要用于文件压缩,优化存储空间。

  • 读写器

  每种文件类型(WAL,TSM,tombstones等)都分别拥有自己格式的读写器。

好,就这些了,希望对你有帮助。

本文github地址:

https://github.com/mike-zhang/mikeBlogEssays/blob/master/2017/20170423_Influxdb数据存储描述.rst

欢迎补充

Influxdb数据存储的更多相关文章

  1. SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储

    前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...

  2. 物联网架构成长之路(33)-EMQ数据存储到influxDB

    一.前言 时隔一年半,技术变化特别快,学习也要跟上才行.以前写过EMQ数据转存问题,当时用了比较笨的方法,通过写插件的方式,把MQTT里面的数据发送到数据库进行存储.当时也是为了学习erlang和em ...

  3. [转帖]时序数据库技术体系 – InfluxDB TSM存储引擎之数据读取

    时序数据库技术体系 – InfluxDB TSM存储引擎之数据读取 http://hbasefly.com/2018/05/02/timeseries-database-7/  2018年5月2日   ...

  4. [转帖]时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入

    时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入 http://hbasefly.com/2018/03/27/timeseries-database-6/  2018年3月27日  ...

  5. InfluxDB学习之InfluxDB数据保留策略(Retention Policies)

    InfluxDB每秒可以处理成千上万条数据,要将这些数据全部保存下来会占用大量的存储空间,有时我们可能并不需要将所有历史数据进行存储,因此,InfluxDB推出了数据保留策略(Retention Po ...

  6. Influxdb的存储引擎

    创建Influxdb数据库时,我们可以看到下面选项,每个选项的含义就是本文要描述的: Influxdb内部数据的存储可以使用不同的存储引擎.当前0.8.7版本支持的是LevelDB, RocksDB, ...

  7. 时序数据库技术体系 – InfluxDB TSM存储引擎之TSMFile

    本文转自 http://hbasefly.com/2018/01/13/timeseries-database-4/ 为了更加系统的对时序数据库技术进行全方位解读,笔者打算再写一个系列专题(嘿嘿,好像 ...

  8. [转帖]influxdb和boltDB简介——MVCC+B+树,Go写成,Bolt类似于LMDB,这个被认为是在现代kye/value存储中最好的,influxdb后端存储有LevelDB换成了BoltDB

    influxdb和boltDB简介——MVCC+B+树,Go写成,Bolt类似于LMDB,这个被认为是在现代kye/value存储中最好的,influxdb后端存储有LevelDB换成了BoltDB ...

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

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

随机推荐

  1. tomcat8.0.15+spring4.1.2的集群下共享WebSocketSession?

    环境:nginx+Tomcat服务器 A B C   问题:如果用户 1 访问由服务器 A socket服务  ,用户2 由服务器 C socket服务  ,此时如果用户 1, 2 想通过  sock ...

  2. SharePoint 如何导出部署的场解决方案

    前言 当我们在做服务器场迁移或者备份的时候,经常需要场中部署的解决方案包,然而,很多时候,我们无法找到这些解决方案包.很多解决方案在部署的时候,可能就已经删掉了,很多解决方案由于时间久远,我们不知道哪 ...

  3. Swift - CALayer的contents属性动画

    Swift - CALayer的contents属性动画 效果 源码 https://github.com/YouXianMing/Swift-Animations // // LiveImageVi ...

  4. 《TCP/IP网络编程》

    <TCP/IP网络编程> 基本信息 作者: (韩)尹圣雨 译者: 金国哲 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115358851 上架时间:2014-6- ...

  5. [转]ThinkPHP的CURD易忽视点小结

    转自: http://www.oschina.net/code/snippet_2285640_44437. 1.使用对象的方法插入数据 D用法. $Form = D('Form'); $data[' ...

  6. java中使用MD5进行加密 BASE64Encoder 编码

    原文地址:http://www.cnblogs.com/weiwangnuanyang/articles/4326336.html java中使用MD5进行加密     在各种应用系统的开发中,经常需 ...

  7. 日历控件My97DatePicker WdatePicker屏蔽 onchange的解决方法

    http://www.cnblogs.com/wan-feng/archive/2013/12/13/3473439.html 受下面文章的启发,使用DatePicker自带的年月日相关的change ...

  8. boost::asio::ip::tcp实现网络通信的小例子

    同步方式: Boost.Asio是一个跨平台的网络及底层IO的C++编程库,它使用现代C++手法实现了统一的异步调用模型. 头文件 #include <boost/asio.hpp> 名空 ...

  9. go语言之进阶篇接口的继承

    1.接口的继承 示例: package main import "fmt" type Humaner interface { //子集 sayhi() } type Persone ...

  10. python 字符串操作常用函数总结

    说明:并不完善,只是记录自己使用到的,没使用到或会用的不会出现在本文. 1.字符串截取 (1)基于索引 s = 'ilovepython' s[0]='i' s[-1] = 'n' (2)取其中一段 ...