在这里插入图片描述
之前一直对时序数据库十分好奇,觉得一定是十分高大上的东西,后来机缘巧合之下,有幸参与到一款时序数据库的开发工作中,虽然不是最核心的部分,但是一番折腾过后,对时序数据库有了一些了解和心得,在这里分享出来,希望能对大家有所帮助。

时序数据库的定义

顾名思义,时序数据库就是处理时序数据的数据库——TSDB(Time Series Data Base)。
现在问题来到了:什么是时序数据?
带时间属性(时间戳)的数据就是时序数据。
时序数据几乎无处不在:监控数据、行驶轨迹、设备传感器…

时序数据的特点

数据特点很重要,直接决定了时序数据库的应用场景、设计思路、以及。。上限。

  1. 数据量大
    量大是因为以下两点:
    1.1 频率高
    时序数据的数据量和采样频率密切相关,你想要的结果越准确,就必须越频繁的采样。举个例子:你要监控一辆车的行驶轨迹,轨迹是连续的,数据是离散的,还原轨迹时,只能把采集到的各个时间点的坐标连起来。于是乎,你想要的轨迹越精确,就必须越频繁的采集坐标点。
    可以1秒钟1个点,也可以1毫秒1个点。
    1.2 持续时间长
    不同于与人相关的数据,比如交易数据,交易完成了,就不在产生新数据了。但是,在时序场景下,数据是几乎没有尽头的。比如:我们要监控一台机器的温度,除非这台机器不存在了,否则温度数据会一直一直源源不断的产生下去。
  2. 每一条数据都带有时间戳
    时间是不可逆的,时间戳是固定的长度,也就是说,时序数据库中至少有一列是递增的固定长度的整形。
  3. 数据的时效性
    时序数据越旧越没用,我们会十分关心最近一小时/一天的数据,但是我们几乎不会关心半年前的数据。
  4. 数据处理
    时序数据很少涉及到事务,且处理数据的函数也基本上都是围绕着”时间“进行的,比如降采样、聚合、同环比、空值填充等等。

传统数据库处理时序数据

对,我和你的第一反应是一样的,时序数据相比普通数据,无非是多了个时间戳而已,常见的关系型数据库或者kv数据库也可以处理时序数据啊,比如mysql,我可以把时间列定义为主键。比如Hbase,我可以把时间戳写到Row-Key中。
是的,没问题,也确实有人(还不少)这么做过或者正在这么做。
先来看一个例子:
我刚毕业那会儿(2015年),在第一家公司碰巧就是做的一个数据处理平台。大概场景是:将遍布在各地的网络完全设备源源不断上传上来的数据(网络攻击、流量、设备自身监控)进行存储计算、展示。
很多年后,才意识到,这就是标准的不能再标准的时序数据了。
一开始,我们用的mysql,因为数据量巨大,每隔一段时间,就需要把次新数据进行抽样后重新存储,再老的数据就删掉。
后来数据规模进一步扩大,我们开始使用公有云上的HBase,尽管精心设计了RowKey,但是仍然无法支持很多场景的查询,比如TopKey,这在监控领域是最常见的需求了,然后我们不得不开发了很多co-processor(协处理器,现在想想,和UDF差不多一个意思)。
然后无论是Mysql也好,HBase也好,随着数据量的变大,存储成本越来越高,不得不说,公有云是真贵。。
再后来,我就离开了。。。

传统数据库无法应对时序数据

说了一大堆废话,总结一下,为啥传统数据库无法应对时序数据场景,想来想去,无非两个原因:

  1. 存储成本
    以mysql为例,mysql的数据时按行存储的,一条数据存储在一起,比如这样一张表:
timelatitudelongitude
123453060
123463159

在mysql的数据文件中大概可以认为是这样存储的:12345,30,60|12346,31,59
可以看到,这种行式存储,很难进行数据压缩,所以对海量的时序数据来说,其存储成本会非常高。
那么HBase会不会好一些?HBase是(伪)列式存储
会好一些,但不会好太多,原因有两个,一是因为HBase不是纯粹的列式存储,它是把一个列族内的数据存储在一起了。二是,HBase是按照key-value进行存储的,key有很大的overhead,每个key中都会包含row-key和column-family以及一个隐藏的时间戳,对时序数据来说,本来就是带时间戳的,这无形中将数据膨胀了很多倍。

  1. 性能
    mysql因为索引是按照B+Tree进行组织的,随着数据量的上升,B+树的高度会增加,性能随着树的高度升高而急剧下降。所以,平时会有mysql单表不要超过xxx行的最佳实践之类的说法。
    对于Hbase来说,因为所有的查找都是基于row-key的,所以但我们需要按照某一列进行查找时,速度就会变得不可忍受。

在解决以上两种问题的过程中,慢慢衍生出了时序数据库。

时序数据库的三种设计思路

改造关系型数据库

既然mysql这类关系型数据库无法应对时序数据场景,那么我们针对存储成本和形成这两点做专门的优化,看看是不是能满足我们的需求。
沿着这个思路,我们看看有什么办法。

  1. 存储问题
    既然行式存储无法进行数据压缩,那么我是不是可以定期的把历史数据读出来,然后在内存中把多行数据按列进行压缩编码成1行数据,再写回到数据库中。
    当然,理论上没问题,实践起来,可能没想象的那么美好。虽然解决了数据存储空间的问题,但是数据压缩之后的二级索引就都没了,而且,把数据读出来,压缩,再写回去,总归是要耗费大量系统资源的,再而且,面对一份压缩后的没有索引的数据,查询起来也比之前费劲多了。
  2. 性能问题
    这个基本就是按照分库分表来,把B+树的规模限制在很小的范围内,用作分表的key自然是时间戳了,比如可以每天一张表。
    沿着这个思路做的比较成功的一家数据库是:TimeScaleDB, 文档写的比较好,详细阐述了其设计思路,大家有兴趣可以看一下。

改造key-value数据库

再来看看另一条路,怎样改造HBase呢?

  1. 存储问题
    HBase的问题如前面所说,在于key中的冗余数据太多,得想办法减少一些。
    以1小时3600条数据为例,按照传统的做法,会在HBase中存3600行,每行都有很多冗余数据。
    我们可以这样改造:
    row-key中只存到小时粒度,秒都扔到列上,也就是说,之前是3600行1列,现在是1行3600列。
    好吧,对于底层的key-value来说,冗余数据并没有减少,接下来的关键一步是,定期把一行的3600列读出来,压缩成两列:时间列和数据列,思路上其实和上面讲的改造关系型数据库差不多,当然弊端也差不多。
  2. 性能问题
    既然HBase不支持二级索引,那么可以把需要的过滤信息扔到row-key上,比如,每条数据可以带上一个或者多个tag,把tag放到row-key上,查询的时候,可以指定tag为过滤条件,类似select value from table where tagA=xxx and tagB=xxx. 本质上还是扫描rowkey,但是因为rowkey和是有序的而且在region上有统计信息(min、max、bloomfilter等),查询起来比扫描value快很多。
    沿着这条路做的比较成功的数据库是OpenTSDB,虽然文档写的比较乱,有兴趣的还是可以去瞅瞅。

我们看到,无论是基于PostgreSQL魔改的TimescaleDB,还是基于HBase进行魔改的OpenTSDB,都因为受限于其底层架构,无法做到完美适配时序数据场景,始终是有遗憾的。
于是乎,有了第三条路,从0开始,针对时序数据的特点开发一款原生的时序数据库。

原生时序数据库

InfluxDB

原生数据库近年来越来越多,历史悠久且应用广泛的当属InfluxDB,遗憾的是,InfluxDB的集群版并不开源。
和OpenTSDB类似,InfluxDB也使用tag作为时间序列(timeseries)的倒排索引,检索数据时,大致上可以按照这样的方式来:

select time, value from table where tag_key = tag_value

一个数据点的格式,类似这样:

weather province=jiangsu,city=suzhou,district=huqiu temperature=27 12312431243412

数据写入时,给tag建立倒排索引,查询的时候就可以用了。

InfluxDB发展了很多年,时至今日,已经拥有很完整的生态,从数据采集、数据传输、数据处理、数据存储、数据分析、流计算、数据展示、订阅发布等等,诸如kafka、flink、prometheus、grafana、mqtt等等,数据和监控领域常见的软件几乎都可以和influx进行集成。这一点其他数据库还有很长的路要走。
为了追求写入效率,InfluxDB采用了LSM tree的架构,这在大数据领域是比较常见的做法。
在数据存储上,InfluxDB支持多种常见的数据类型,如int long float double boolean text等等,针对每种数据类型的特点,InfluxDB会自动采取最优的压缩方式,尽可能的降低存储成本。
除了数据的读、写、存之外,InfluxDB还有一个叫做Kapacitor的组件,这是一个类似流式计算的框架,可以通过简单的脚本创建计算任务,并且支持非常非常多的中间算子和数据输出形式。
多年来,Influx一直在持续更新,其下一代全新的时序数据库(InfluxDB IOx)也在研发中了,完全开源,感兴趣的可以在git上去瞅瞅。
另一个比较赞的地方是,InfluxDB的文档写的比较好,看起来很舒服。

TDEngine

TDEngine是一款国产的由涛思数据开源的原生时序数据库,在国内用户据说也比较多,单机和集群版也都开源了,性能据说比InfluxDB好很多,这个我没测试过,了解的也不多。

IoTDB

IoTDB是清华软件学院开源的时序数据库,目前已经是Apache的顶级项目,开发者非常活跃,版本更新也比较快。有幸提交过几行代码,因此对IoTDB有些许了解。
之前做过测试,IoTDB在性能上(单机版)比InfluxDB高4倍左右,但是从原理上说,大家都是LSM架构,都有时间线索引,性能上的差别,应该是来源于IoTDB在很多工程细节上的精心优化。
大体上说,IoTDB和其他时序数据库没太大区别,也是分几大块:元数据、存储引擎、查询引擎、数据压缩、生态周边。

元数据

IoTDB的元数据(时间序列索引)组织方式并不是常见的Tag平铺类型,而是一种树形的结构。省去了TagKey,对比一下:

weather province=jiangsu,city=suzhou,district=huqiu temperature=27 12312431243412

在IoTDB中是下面这样的:

root.jiangsu.suzhou.huqiu.weather.temperature 12312431243412,27

这种模式下,比如我要查苏州所有区的天气数据,那就得这样写了:

select root.jiangsu.suzhou.*.weather.* where time = xxx 

本质上和tag一样,都是对时间序列的一种索引,相比之下,我还是更喜欢tag,总觉得这种树形结构有点反人类(个人感受,别打我)。

存储引擎

IoTDB也是采用LSM的架构,相比其他数据库做的一个优化是,IoTDB将顺序数据和乱序数据分开存储,写到不同的数据文件中,然后后续做compaction的时候再合并到一起,这种设计在写入和查询性能上都有不少改进,因为时序数据的采集链路受限于网络原因,一定范围内的乱序数据是不可避免的。

查询引擎

IoTDB在查询上采用了MPP的架构,可以胜任大数据量的并行查询。除了常见的SQL外,IoTDB还内置了大量的对于处理时序数据非常有用的UDF,并且支持对时序数据分析常见的需求:降采样、时间戳对齐、时间窗口、各种聚合函数等等。

数据压缩

IoTDB也支持各种常见数据类型以及对应的压缩算法,在海量时序数据的场景下,高压缩比无疑会节省很大的存储成本。

生态周边

IoTDB还在发展中,生态这一块还赶不上InfluxDB,但是也已经支持不少周边产品了,如hive、spark、flink、kafka、grafana等。
除此之外,IoTDB实例之间也支持数据传输,这在云边一体的场景下还是很有用的。

最后

关于时序数据库,大概就说这么多吧,具体的,大家有兴趣可以去各家的官网学习,这里容我再吐槽一下,国产的这两款时序数据库虽然在性能(技术)上已经超过InfluxDB了,但是,官网略显寒碜,得多学学InfluxDB,毕竟是门面。

最后的最后,希望对大家有所帮助,都是比较概括的描述,不涉及太多技术细节,单纯就是希望大家对时序数据库有个概念上的认识,不至于像我当初听了时序两个字之后一脸懵,紧接着觉得无比高大上。真的了解之后,就比较平淡了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐