👆戳蓝字“守护石”关注
当我们一提到InfluxDB,就会想到时序数据库(TSDB),InfluxDB的确就是为TSDB而生,不过刨根问底,它到底为什么就是为时序存储而生,这里面就很有学问了,我正好近期在做这方面的研究,那么我就简单明了且深入的为看官们分析一二。
数据模型
1.时序数据的特征: 时序数据应用场景就是在时间线上每个时间点都会从多个数据源涌入数据,按照连续时间的多种纬度产生大量数据需要写入存储。传统的RDBMS数据库对写入的支持都是按行计算,B树结构索引,难以做到大批量的写入支撑,更何况多纬度数据连续的涌入存储,因此时序数据的存储设计都会将目光放在LSM-Tree或者列式的数据结构存储方向。
再看时序数据结构:数据源(DataSource)+指标项(Metric)+时间戳(TimeStamp)=数据点,每个数据点就是时间线上的一个指标测量点。如果从一个二维图来表示的话,就如同下图所示,横轴代表了时间,纵轴代表了测量值,而图中的每个点就是指标测量的数据点(Point)了。

上图可以表述为:数据源(动环检测-高新区机房-数据区)—指标(湿度),数据源(动环检测-西咸区机房-计算区)—指标(湿度)在连续时间点(TimeStamp)上的时序图。其中动环检测代表了数据源的业务领域,机房、区域代表了数据源的标签(Tag),动环检测+机房+区域就确定了唯一的时序数据源。
2.OpenTSDB和HBase: 时序数据库除了InfluxDB之外,还有另一个比较有名的时序库—OpenTSDB,OpenTSDB就是基于HBase平台的时序数据库实现,在我以前的文章中多次分析过HBase的内部机制,它的特征就是源自Google BigTable的数据模型,以列簇为数据组织和存储的整体单元,我们可以理解列簇就是一张Excel宽表,表中每一个单元格就是由K/V组成,KEY由行键+列簇名+列名+时间等构成,那么对KEY排序后,类似单元格的K/V自然就形成了按照行键、列簇名、列名、时间的顺序排列。非常方便地按照行键去抓取列簇的一组列值。
如下图所示:就是标准的K/V结构

我们从时序数据的特征可知, 数据源+指标+时间戳就可以确定到一个数据点,那么在OpenTSDB的设计中,这个组合就是行键。不过由于时间戳是连续不同的,就会导致每个K/V的行键都不同,这会产生非常大量的单列数据,因此OpenTSDB进行了优化,将行键中的时间戳按照小时计,按照秒划分为3600个列,这样列簇的一行代表了一个小时的时序数据,每一列代表那一秒的值。
从机制设计的角度看, OpenTSDB基于HBase的特性进行优化,已经做得很好了。但是HBase的本质还是K/V作为一个原子单位,由多个K/V形成Block,再由Block形成HFile文件,每一个K/V都会因为KEY的构建带来大量的冗余,而且KEY是无法在通用的压缩算法上进行有效压缩的,最终一定会占用更多的存储成本,本质问题还在于无法对时间戳(Timestamp)进行独立剥离处理。
3.InfluxDB数据模型: InfluxDB并没有打算完全搞一套全新的数据存储理论体系,而是在参考HBase的LSM-Tree数据模型后,建立一套适合时序数据的存储架构,名叫TSM。我们再重复一下HBase的数据模型机制,追加WAL,在内存中建立批量写入的数据缓冲区MemStore,定期或写满的情况下MemStore冲刷(Flush)到磁盘StoreFile,StoreFile再定期进行文件合并,做归并排序并完成记录去重。这种基于LSM-Tree结构的数据模型能极大提升写入的性能,很多NoSQL系统都是这个操作路子。
InfluxDB也不例外,继续这种LSM-Tree结构套路,时序数据写入时先追加WAL,然后再写入Cache,然后定期或写满的情况下冲刷(Flush)到磁盘TSM文件。
我们看它跟HBase不同的地方,主要在于数据结构的设计,HBase的MemStore对写入的数据直接封装成K/V然后组成一个个更大的Chunk块 。这种结构最大的特点就是HBase以一种近乎于通用的方式顺序地将原子单元(K/V)排列并封装成更大的Chunk块,数据之间设计得非常松散,随机性查找不依赖数据结构优化,而是依赖索引基础上的扫描,属于万金油型,任何上层应用都可以拿来一用,例如:各种宽表业务的扫描查询、聚合分析或者设计出按时间线分析的TSDB。
但是InfluxDB在Cache中重新优化了结构:Map集合<数据源,Map集合<指标,List列表<时间戳:数据值>>> ,我们可以看到是一个Map套Map再套List的结构,第一层Map就是序列(Series)集合,Series就是InfluxDB定义的数据源(measurement + tagset),第二层Map为指标集合,InfluxDB定义指标为Fields,第三层就是某个数据源的某个指标的时间线记录列表了。
因此我们可以看到InfluxDB首先根据时序数据的特征,进行了数据结构上的重新调整来适应这种时序特征的需要,那么就有了以数据源为索引去定位指标,以指标为索引去定位时间线上的数据列表。如下图所示。

InfluxDB内存中Cache会Flush到TSM文件,TSM文件中也会根据上述结构建立磁盘文件的数据块排列和索引块排列,每个数据块就可以分别是Timestamps列表和Values列表组成,那么就可以对TimeStamps列表进行单独压缩(delta-delta编码),Values列表具有相同的类型Type,可以根据Type进行压缩。索引块建立数据块与Series之间的关系,并通过对时间范围的二分查找的方式快速定位待查数据的数据块位移。
其实InfluxDB除了TSM结构之外,还有TSI结构,TSI主要是进行倒排索引,例如:通过动环检测-高新区机房为条件查询高新区机房各个区域的各项指标,或者也可以通过动环检测-数据区域为条件查询所有机房的数据区域的各项指标。时序库在这种按数据源标签(Tag)分类的分析查询上会表现得非常高效,这也是InfluxDB充分应对了时序数据业务的需要。TSI以后有机会再写。
分布式
InfluxDB在集群方面闭源了,这的确是件非常可惜的事情。不过可以理解,任何技术创业公司都要谋求生存和发展,商业输血是必须的,希望有一天InfluxDB的母公司能被巨头收购,然后再次完全开源。我在以前专门讲过一期HBase和Cassandra的对比,有兴趣的朋友可以看看:HBase和Cassandra的分布式架构深度对比。
InfluxDB更倾向于去中心化的分布式, 里面有我对两者分布式的对比,而InfluxDB更接近于Cassandra,Cassandra是一套去中心化的分布式架构,而InfluxDB包括了Meta节点和Data节点,但是Meta节点看似是主节点,但作用很薄弱,主要存储一些公共信息和连续查询调度,数据读写问题主要还是集中在Data节点,而Data节点之间的读写,更贴近于去中心化的方式。
InfluxDB是CAP定理中的AP分布式系统, 非常侧重于高可用,而不是一致性。其次InfluxDB的主要是两级分片,一级为ShardGroup,一级是Shard,ShardGroup是个逻辑概念,代表一个时间段内的多个Shard,在创建InfluxDb数据库(DataBase)和保留策略(RETENTION POLICY)后就确定了ShardGroup的连续时间段,这就相当于对时序数据首先进行了按照时间段范围的分区,例如1个小时作为一个ShardGroup。
但是这1个小时内的数据并不是存储在一个数据节点上,这点就大大不同于HBase的Region了,Region首先会朝一个数据节点使劲地写,写饱了才进行Region拆分,然后实现Region迁移分布。而InfluxDB应该是参考了Cassandra那一套办法,使用了基于Series作为Key的Hash分布,将一个ShardGroup的多个Shard分布在集群中不同的数据节点上。
下面定位shard的代码中key就是Series,也就是数据源(measurement + tagset)。
shard := shardGroup.shards[fnv.New64a(key) % len(shardGroup.Shards)]
上面代码中shardGroup.Shards就是ShardGroup的Shard数量,该数量为N/X,N是集群中的数据节点数量,X就是副本因子。Hash分布方式就能充分利用集群每一个数据节点的读写优势。
InfluxDB是最终一致性, 在写入一致性上实现了各种调节策略Any、One、Quorum、All,和Cassandra如出一辙,并且加强了协调节点的Hinted handoff的临时队列持久化作用,这就是完全为了高可用性,即便真正的副本节点故障了,就先在协同节点的Hinted handoff队列中持久化保存,等到副本节点故障恢复后,再从Hinted handoff队列中复制恢复。就是为了达到最终一致性。
另外InfluxDB和Cassandra3.x及以前版本一样,具有后台碎片修复的反熵功能(anti-Entrory),不过有意思的地方是Cassandra在新的4.0版本已经不在保留后台读修复的功能了,而且之前版本也不推荐启用,因为具有了主动读修复能力,后台读修复作用不大,而且还影响性能。当然InfluxDB是不是和Cassandra一样,在读取的过程中进行了主动读修复,因为是闭源系统,这点上我还不太清楚。
InfluxDB在删除问题上会表现的非常薄弱, 只允许删除一个范围集合或者Series下的一个维度集合,这也是这种时序结构的设计使然,对单个Point的删除会很麻烦,成本很大。
删除方法上应该也是参考了Cassandra的Tombstone机制:去中心化的问题就是怕大家都删除副本后,某个正好处于故障中的副本节点又恢复了,它不知道发生了什么事情,但修复过程中它会认为被删除的副本大家弄丢了,而去让大家恢复,有了Tombstone标记的长期存在,那么未删除数据的故障副本节点,当恢复后也就知道发生删除的情况,进行自身节点副本的删除。




