接下时序数据存储的挑战书,阿里HiTSDB诞生了

近日,2017中国数据库技术大会在京召开,来自阿里巴巴中间件团队高级技术专家钟宇(花名悠你)在数据存储和加速技术专场分享了题为《时间序列数据的存储挑战》的演讲,主要介绍了时序数据的由来,时序数据处理和存储的挑战,以及目前业界的通用做法。在案例展示部分,他结合阿里内部业务场景和时序数据的特点,讲述阿里时序数据处理和存储所面临的问题以及解决问题的过程,以及不断应对挑战慢慢形成HiTSDB的过程。

演讲全文:

钟宇:大家好,我叫钟宇,花名悠你(Uni),来自阿里巴巴中间件(Aliware)团队。首先我大概给大家介绍一下今天的分享内容,共有五个方面,首先跟大家介绍一下什么叫做时间序列,时序数据这个领域是一个比较专的领域,但同时它的范围又很广,因为我们平时遇到的数据里头有相当大的一部分都是时序数据,但是它又相对来说比较专,因为它不像我们一般的数据库什么都可以做,所以时序数据这个领域还是蛮特别的。

接下来我会说一说时序数据的存储和分析的一些方案,因为处理时序数据有很多的方案可以用各种各样的东西来做。给大家举一个例子,我高中的时候刚开始学计算机,我当时特别不能理解,为什么世界上会有数据库这样的东西,因为有个东西叫excel,它和数据库一样是一个表格,我只要把数据库放上去,在里面搜索一个数据比数据库还快,所以我想为什么要有数据库。

等到用的东西多了以后会发现,数据的存储和分析,excel是一种方法,数据库也是一种方法,针对的数据量和应用场景是不一样的。时序数据其实也是一样的,就是说我们可以有很多种不同的方式来存储和分析时序数据,就好比是分析一般的表格,用excel可以,用MYSQL也可以,完全是针对不同的场景做出的不同选择。

接下来我会介绍一下时序数据库,因为时序数据库是时序数据存储和分析的一个非常重要的工具。我们考察过很多时序数据库,包括influxDB和OpenTSDB),我们对此做出了改进来适应阿里的特殊应用场景,接下来我会着重介绍一下阿里对时序数据库的一些改进。

目前来看时序数据是比较年轻的领域,没有什么标准,整个行业发展也比较稚嫩,有很多方面并不完善,所以最后看一下,它还有哪些方面的发展是需要我们来继续的。

时序数据特征及常用应用场景

先跟大家聊一下什么叫时序数据。可能大家平时了解的不太多,这个东西很简单,就是时间上分布的一系列数值,关键字是数值,我们一般认为的时序数据是什么时间发生了什么事情,但是在时序数据这个领域里定义的时序数据全都是跟数值有关的。也就是说如果只是一个带有时间戳的一条数据并不能叫做时序数据。举个例子,比如像我早上8点半上楼吃了个饭这条记录,相当于一个日志,这个本身不构成一个时序数据,但是如果某个餐厅早上点半同时有50个人在那里吃饭,这个50加上餐厅的信息再加这个时间点就构成了一个时序数据。

更明显的例子比如说股票价格,每一个时刻每一支股票都有一个交易的价格,这种其实是时序数据。还有很经典的应用是广告数据,广告数据中很多像PV、UV一类的东西,这些都是在某一个时间点上一个数值型的东西。然后就是一些工业和科研上的东西,气温变化、工业上的传感器的数据都是这样的。

最近还有一个比较火的概念,大家会想到物联网,物联网的数据大部分都是时序数据,所以从这个角度来说,时序数据可能占我们平时需要处理的数据量中相当大的一部分。

举个广告的例子,我们会发现一个时序数据除了有一个时间点以外,它还有一些别的部分,从头开始说,首先是要有一个东西来标注数据源,举个例子做广告,广告有很多的来源,在这个图表里大家会看到是AD source1、2、3,这3个是不同的广告源,为了区分不同的广告源在上面打了一些标签,1这个网站实际上是面向google发布的,面向的群体是男性,这是一个广告源,这些标签就标识了唯一的数据源,每一个数据源它都有很多的指标,我们把它叫一个指标或者一个测量。

我们在这个广告源上测量了很多次,比如说会测量它被查了多少次、会测量它被点击多少次、会测量它产生了多少收入,我们每个广告源都做了三次的测量,这样每个广告源就产生了三个时间序列,我们叫time series,每个广告源有三个时间序列,在这个时间序列上在每一秒钟都会对它进行一次测量,所以每次测量会产生一个时间序列的值,我们把它叫做一个点,基本上时序数据的样子就是这样的。

然后再来看建模,实际上通用的建模方式有两种,其中的一种是单值。实际上我们是针对不同的东西来建模的,多值的模型是针对数据源建模,我们每一行数据针对的是一个数据源,它的三个被测量的指标在列上,所以每一个数据源,数据的来源在每一个时间点上都有一行,这就是多值的模型。

还有一种模型是单值的模型,单值的模型我们是把它测量的精确到时间序列上,也就在时间序列的每个时间点上只有一个值,所以是个单值,也就是说对于多值模型来说它每一行数据对应的是一个数据源,对于单值模型来说它对应的是一个时间序列,实际上多值模型对应的是一个数据源在一个时间点上就会产生一行数据,而在单值模型里一个数据源上面的每一个指标会产生一行数据。

下面牵扯到时间序列处理的一些比较特别的东西,这在我们的传统数据库里有可能是没有的,叫插值和降精度,刚才我们已经看到,时间序列会分布在一些时间线上,数据源和测量指标确定了的话,时间序列是随着时间轴往后分布的,实际上它的采样在一个典型的场景里是固定时间间隔的,它中间一些点做处理会牵扯到插值和降精处理。比如说中间丢失了一个点,比较简单的方法是中间插一个值,常用的方法是线性插值,就是在时间轴上画一个直线中间的点就插出来了。

另一个叫降精度,例如我们有个按秒采样的时间序列,显示时间范围是一年的数据,为了便于查看,需要把时间精度降到一天。比如我们只选这一天中的最大值或者最小值或者平均值,作为这一天的气温,也就是最高气温,最低气温和平均气温的概念。用算法或者把时序数据转换成精度比较低的时间序列以便于观察和理解它,这是在传统数据库里没有的一种方式。

这个东西就跟传统的数据库有点像但是聚合方式不一样,比如这里有很多时间线,实际上时序数据的聚合是在时间线的维度上的,而不是按点的,我们是在处理平时处理的空间聚合的话,一般是把很多数据点按照一个个聚合起来,而实际数据处理的时候一般会把它抽象的点连成线就是刚才看的时间序列,每个数据源在一个测量值上会产生一行时间线,加上时间序列,如果是根据某一个维度上的测量的话,在同一维度就能调成线就把时间序列处理出来了。

基本上插值,降精度,聚合就是时序数据处理的最常用的方式。

我们再看看特点,如果我们只是做刚才那些东西的话其实会很简单,但是再结合了时间序列数据的特点以后,有一些简单的事情就会变的很复杂。首先第一是时间序列会持续的产生大量的数据,持续的产生什么意思呢?因为我们往往对时间序列来说是定时采样功能,比如我们说气温的波动,每秒测量一次,一天是86400秒,如果是我们做系统监控,或者像气温这样的科学仪器持续的调数据的话,24小时都要用,平均每一个仪器仪表在一个时间点上产生一个数据点,一个仪表就产生86400个数据,如果把全国各个县都布一个采样点,那一天数据就上亿了,实际上大家作为气象采样来说每一个县对应一个温度传感器显然有点不够的,可能我们是每一个街道甚至每个小区都有这样的传感器,那么这个数据加起来实际上是一个非常惊人的数字。

还有另外一个东西是数据产生率是平稳的,没有明显的波峰波谷。全国布满了都是传感器,这些传感器只要是不坏的是持续传数据的,所以我们无法期望它会像我们的交易系统一样每年双十一有一个明显的峰值,平时就没什么事了,很少有这样的情况,这个特性对我们做优化来说有优势也有劣势,优势就是我不需要考虑太多的削峰填谷,不需要为突发的大流量准备太多的资源。但不利的地方是,我们有些算法是很难在里头腾出时间来做一些整理的工作的。举个例子,如果把数据存在一个像LSM Tree或者SSTable一样的东西的话,当compaction发生,希望把两个块合并起来的时候,这时候在持续数据这个场景下上面我们写数据可以是很高很高的数据来写,如果底下的IO太厉害的话就会产生雪崩的效应导致写入延迟或者失败,需要做一些限流的处理。

还有一个特点是近期数据的关注度更高。时间越久远的数据就越少被访问,甚至有时候就不再需要。

看到这里我们会发现实际上时序数据我们可以用一个单表把它建立起来,我们通过在每一行数据上加很多标签来把这行给挑出来。所以我们展示的时候往往就是对标签做聚合计算。

阿里时序数据场景及解决方案演进历程

首先举个例子,阿里巴巴内部有一个系统叫鹰眼,这个系统实际上是做一个机器的,简单来说阿里内部有很多服务器组成的一系列很大很大的集群,因为机器太多了,所以永远会有失败的时候,要么是程序失败要么是鹰眼失败,所以会有一个系统把这些所有的机器应用还有链路这些接入起来,时序数据也是其中的一部分,因为它需要用这些时序数据来监控整个系统的状况,其中包括里头的服务,所有服务器的指标,比如说每个服务器的CPU利用率,内存使用率,还包括服务器上所有应用的指标,比如说对应用QPS精确到每一个KPI,每一秒被调用了多少次等等。这些东西会产生很多的时序数据,写入的峰值是570万点/秒,平均写出来300万,虽然说时序数据一般来说会比较平的,但是还是会有些波动。

因为在阿里内部系统请求太多,会产生服务降级的情况,服务降级也就是说当一个集群发现它的服务能力不足以支撑的时候,它会选择另外一种消耗更少的方式,比如原来是用一秒钟采样的,如果发生服务降级了是5秒钟采样或者1分钟采样,会集中在分钟的边界发送,导致在那一小段时间内有一个峰值。因为管理的机器很多,所以会产生很多的指标。

比如说对于服务器监控会有处理器、会有IO,对于应用的监控有QPS,不同的指标加起来有上万个。更大的指标是因为有很多应用,每个服务器上面可能有几十个应用,所以加起来几千万个时间序列,每个时间序列平均有5个维度,一个时间序列把几万台机器挑出来,举个例子说,我们部署的机房,在这个机房里头内部的IP系统上,机房、哪个机架、IP是多少、在上面哪个应用以及这个应用里哪个具体的指标,是QPS还是UV之类的那些东西,大概平均5个维度就能把一个时间序列挑出来,所以其实总的维度数量并不是很多,但是每个维度里头它的离散度很大,比如光是IP这个离散度就有几十万。因为这个鹰眼系统是内部的系统,使用者是内部的管理人和程序员,每秒大概聚合几百次,这个场景是我们内部比较完整的时间序列的场景。

接下来会谈一谈我们自己怎么样在这样的场景里做存储分析。

我们内部项目在做时序数据的存储时,最早思考的是把它保存在关系数据库里,这是一个很通常的做法,但是后来发现这个事情可能不太可行,因为大家都知道InnoDB的写入性能是很有限的,我们在一个内部测试大概在24台机器上,因为我们优化很好,而且是存储设备是SSD硬盘,写一秒钟持续写成达到两万左右,和刚才说需要的570万还差的很多,如果我们达到这个存储量级大概需要300台,其实出现这种情况有原因的,首先一个很大的问题是说,B树的索引,索引是一个B树,这个B树有很大的开销,虽然我们可以通过一些办法优化,但是因为我们为了优化时序数据是一个多维数据,我们为了优化所有排列组合的产品,所以我们是很多多列的索引,这些索引每次在写的时候每个都需要更新,所以就会导致很多的IO。

还有一个更糟糕的问题,我们发现存储空间增长的非常快,就算每秒300万的数据,每个数据加起来要240字节以上,300多万×200个字节,也就是说我们一秒钟一个G,这样的话即使很多机器也会被这些数据塞满了,而这还没算上索引。类似于上述提到的时序数据的存储,如果用这个方案的话只能在一些很小的场景上使用,比如接入几十台机器,上面有很少的应用可能一秒钟只会写个几百条数据这样子,这样的数据量可以用innoDB来做。

另外我们还发现,刚才提到时序数据很重要的是降精度,降精度其实特别难优化,因为降精度是在时间序列维度上做的,首先要把时间序列维度拿出来然后在中间插值,而实际上SQL是不知道这件事情的,SQL是按点来操作的。所以如果要做降精度的话需要用一个值查询把那条时间序列单挑出来,插好值之后才能做时间序列之间的聚合,这意味着我们的服务和SQL服务器之间的吞吐量非常非常之大的,相当于SQL只是一个数据通道需要把所有值都拉出来运算一遍。很难把这个东西放到SQL服务器去。所以我们发现把时序数据放在关系数据库上的方案不可行,就考虑了另一个方案。

一开始最大的问题是写的慢,那我们就找个写的快的东西来处理好了,写的快的东西是什么呢?就类似于SStable那种,就不用那种随机操作的方式,就用追加的方式来写,实际上这个东西在写上面是能工作的,因为我们测试了Google 的LevelDB和MyRocks,我们发现Rocks的性能比较强,所以我们在Rocks上测试了一下,大概能达到20万点/秒,而且这是因为MyRocks写入性能的优化不够,它在CPU的核数多于8核的时候可以共用CPU,线程所用比较保守,阿里内部有一个改造,把它改造成写入性更高的东西。即使是这样性能也不是太高,但是勉强够了,因为能达到20万点/秒,不需要用300台机器了,用30台机器就可以搞定。

但是我们很快发现其实没有那么乐观,因为又回到刚才那个问题的,我们需要建很多个索引来保证多维这个事情,如果我们需要建很多个索引的话意味着我们每次写实际上要去更新一个索引,比如为了保证多维查询的性能需要建4-5个索引,等于写入数据调到原来的1/4。所以这个东西也不太可行,而且它有一个问题是tag重复存储其实没有解决,我们压缩完以后平均5个点还需要50个字节,实际上一个点真正的数据只有8个字节,加上时间就是16个字节,差别还是蛮大的。

下面介绍的是在业界经常被人用的方案–Elastic search,这个东西是比较有好处的,数据量不大的时候,会针对每个纬度来做索引,能很快的把时间点摘取出来。它的倒排索引很大,但是这个方案特别流行因为对于很多公司规模小、客观业务规模小的,这个东西会非常有戏,因为它很快,而且有整个开源社区的支持。

我们后来还试了用列式存储的方式保存时序数据,这是特别有诱惑力的一个方案,因为列式存储第一压缩率会比行式高很多,因为它把相似的数据都放在一起了,而且它有一点特别适合时序数据的是因为写入磁盘的数据是不可变的,时序数据恰好不太需要修改。但是后来我们发现使用了以后踩了个坑,Druid或者inforbright那种方案处理某些时序场景合适,但是处理我们那个时序场景不太合适,因为列式存储是把导入的数据累积到一定程度,才会打一个包把它固定到磁盘上的,但是时序数据如果长时间的查询的话这意味着要查该时间段内每一个包。

因为所有维度的数据在每个包里都会存在,比如要按机架的维度来看,我们积累了一万个数据文件,每一个数据文件里头都有非常大的,可能会出现同一个机架的两三行数据。这就很糟糕,实际上列式存储的筛选数据文件机制没有生效,没办法迅速的把一部分数据文件剔除掉。

接下来我们还考虑了这样一个工具,流引擎,其实流引擎是一个非常好的东西,它同时解决了多维计算,反正你能想象的东西它基本上都能解决,除了存储问题,流引擎不是数据存储引擎只是计算引擎,如果事先你的应用没有对你需要查的数据做很好的规划的话,只能事后再补充一个新的拓扑再计算,但是新拓扑无法立即获得数据,比如上一个新的拓扑,是按24小时精度的,一次要看24小时,那么24小时后才能拿到这个数据,在某些场景下面还是很希望把数据保存下来察看的,但是流引擎做不到这一点。流引擎能做到的是能以很高的效率来处理数据,但是提前要把需求预先定义好,没有办法实时计算。

HiTSDB的由来

所以我们后来还是转到了使用一个专门的时间序列数据库的方案上,业界往往把MongoDB之类的东西也算成时序数据,其实那些东西是比较通用的。专业的时序数据库可能是InfluxDB,openTSDB这样的,这两个东西最大的区别是它能把时间线提取出来,它的思路相当于是事先做一系列的标签、先找到时间序列,在这个时间序列上再找到对应的点。时序数据是按照一个时间长度分片来存在一起,所以这样的好处就是它存储的压缩率会比较高,而且是搜索的时候不需要搜索每一行了,搜索的比较少。

最后我们选了openTSDB,可以保证它把一个小时的数据放到一个行里,这样压缩率比较高,能做到每个数据20字节左右。

但是openTSDB用久了发现它有很多劣势:首先,它其实是一个无状态的节点,所以它的Meta DATA实际上在所有节点上都是全量的,所以它占用的内存会很大;其次,时间线数量很多的时候,Rowscan方式做维度查询,这跟列存数据库有点像,但是当查询条件不满足rowkey的前缀时,它的磁盘IO还是有点太多的;第三,在固定的Column中保存一小时的时间点,这个问题是,大家知道它的qualifier存在额外的开销;第四,还有个更大的问题是openTSDB是单点聚合,也说不管你有多少节点,实际的计算,每次计算都放在一个节点上。

所以我们后来做了很多改进,其中第一个是先引入了倒排索引,我们发现用倒排索引的方式能更快的把时间线挑出来,搜索引擎的问题在于它挑的不是时间线,挑的是所有的数据,所以索引就会很大的倒排,如果我们把这个索引指定在时间线上,时间线是几千万的量级,对于倒排索引来说是很轻松的事情,所以我们引入了倒排索引。

但是引入了倒排索引以后还有很多没有解决的问题,而且有很多新问题,比如说一旦引入倒排索引以后,我们为了保证让它能按一致的方式工作,我们其实做了分片,我们用了很多办法,比如binlog写入到HDFS,保证集群的可用性及数据的可靠性。每个分片一个binlog文件还有分片策略的问题,现在简单的说是按照metric加特定的tag,等于是用了一个结合的方式。

之后我们再继续往下做优化,虽然引入了倒排索引,但性能有所提升有限,因为HBase mget的一些性能限制。中间还发现一个问题,是openTSDB的降精度,它保存的永远是原始数据,所以我们又做了预先降精度的功能,把预先降精度的数据用一个东西算好存进去,这样比如我看一小时的时候,预存有半小时了,这样放大只有2而不是原来的3600,这样改动虽然比较小,但是性能提升很大。

回到刚才那个问题,FaceBook有个东西叫Gorilla,它是个高压缩比的算法,它最厉害的是可以把一个时间点压缩到1.37字节,在我们的测试中基本上可以做到2字节以内,这么高压缩比有什么好处?意味着我们可以把最近的数据,也就是说我们用倒排索引找到的一系列时间线ID以后,大部分只要去一个内存的表里头把对应的压缩好的数据块找出来就行了,因为一个时间点压缩到不到两个字节的话,意味着哪怕是每秒300多万的点也就5、6兆一秒就解决了,通过256G内存的机器,这个东西其实提高非常大(不需要去HBase做mget操作)。

这个和倒排索引结合起来,其实就满足了我们的读写了,但是这个东西带来了一个更大的问题,是写穿还是写回的问题,如果你是一个写穿的方式的话,写性能是并没有提高的,也就是说我们并不能这样写,如果我们要做的更激进一点写回系统,一次性写到后面的存储,这样分片和高可用方案就做的很复杂。最后我们还是搞了点小技巧,把共享文件系统上的binlog写入LDFS,即使这样还是很复杂。

大概再提一下,因为我们做了高可用的工作,但是不管怎么样在时序数据库这个领域里头它并不能保证ACID的,传统数据库是有ACID的保证,实际上时序数据库里只能保证一条数据写过来至少被处理一次,尤其是batch写的方式,比如客户一次性提交300条数据过来,第150条失败了,实际上前面149条已经写入数据库了,这个事情在我们这儿是被允许的,前面的149等于是处理了两遍,这样的话至少每条数据会被处理一次。

然后我们还引入了一个分布式聚合引擎,解决了openTSDB的那个单点的问题,然后把零件组装起来就形成了最后的一个产品,我们叫做HiTSDB,它协议上跟openTSDB是兼容的,但是内部已经被我们改的面目全非了。它的主要核心的功能就是倒排索引,缓存还有分布式聚合,最核心的是倒排和缓存,有了倒排和缓存我们就能以很高的速度来处理一个典型的,在最近时间内典型的一个时间序列的查询。

功能演进的思考及不足

因为时序数据刚才跟流引擎对比了一下,如果用流处理的话在写入之前就把所有的东西写进去了,它的缺点是不具备灵活性,优点是很多场景下速度非常快,而时序数据的想法是做后计算,把所有的原始数据都写进去,想怎么算怎么算,想怎么查怎么查,但是对于一些特别大的查询、又特别固定的查询,反复的计算是没有必要的,所以下一步会把一些可以配置的预聚合功能放到数据库里,实际上当你往外查的时候某些东西是已经算好的,你只要把它查出来就好了。

我们还有一些想法是把历史数据的文件存在云存储上,这样我们可以做长线离线分析,这都是我们考虑的事情。

实际上我们还是有很多场景不能很好的应付的,这个我们在内部也发现了一些:
1、发散时间序列,跟我们的搜索团队有过合作的项目,做离线分析的会把他们的指标数据放在压缩上,离线分布会产生几个小时的数据,时序数据会膨胀为几十几百亿,这个东西目前我们是不能很好的应付的。
2、还有一个是事件驱动 vs 定时采样,我刚才说的都是定时采样的,对于高压缩采样是固定20分钟切一片然后把它压缩起来放进去,但是如果是事件驱动的20分钟可能没有几个点,有时候20分钟也可能非常多的点,这样对于压缩很不均衡;
3、还有一个目前解决不了的是高频采样,我们现在的采样精度最多支持到秒,时间精度是支持到毫秒,但采样精度只支持到秒;
4、还有一个问题是,虽然时序数据是单表,但是很多用户希望和现在的SQL数据表互操作,目前这些问题还是都没有支持的,所以未来我们会考虑做成一个存储引擎。

5、还有一个是group by+topN的优化;

  1. 结合事件驱动和定时采样,考虑引进一些列存的思路解决数据驱动的模型,考虑双引擎(一个处理事件驱动一个处理定时采样)。

再说一个比较炫酷一点的事情是硬件加速,最近阿里在搞关于硬件加速的东西,其中有些类似于FPGA,因为FPGA会用一个流式的方式工作,FPGA下面会带一个万兆或者40GE的网卡,特别适合时间序列场景的类似流架构的方式,所以我们考虑采用FPGA的方式构建下一步硬件加速体系,如果这样的话我们有可能做到在一个板卡上做限速,也就是说数据就是以40G的速度流进来,一个固定的数据速率流出。最后稍微聊一下云部署的事情,这些东西最后会上云,提供公共服务。

最后谢谢一下我们团队–阿里中间件时间序列团队,我们的口号是For Your Time。不浪费大家的时间了。谢谢!

1