当前位置: 首页 > 科技观察

分布式时序数据库QTSDB的设计与实现

时间:2023-03-13 22:46:23 科技观察

现有的开源时序数据库influxdb只支持单机运行。面对大量数据写入时,会出现查询慢、机器负载高、单机容量受限等问题。为了解决这个问题,360的基础设施团队在单机InfluxDB的基础上开发了集群版本——QTSDB。一、QTSDB简介QTSDB是一个分布式时序数据库,用于处理海量数据的写入和查询。在实现上,是基于开源单机时序数据库influxdb1.7开发的分布式版本。除了influxdb本身的特性外,它还具有扩容、副本容错等集群功能。主要特点如下:专为时序数据编写的高性能数据存储,兼顾写入性能和磁盘空间占用;类SQL查询语句,支持多种统计聚合功能;自动清理过期数据;聚合操作集;Golang编写,无其他依赖,部署运行简单;节点动态水平扩展,支持海量数据存储;副本冗余设计,自动故障转移,支持高可用性;优化数据写入,支持高吞吐量;2.系统架构1.LogicalStorageHierarchyInfluxDBArchitectureHierarchy***是数据库,数据库底层根据数据保留的长短划分不同的保留策略,在数据库下形成多个存储容器,因为时序数据库和时间维度关联,所以相同保留期的内容存储在一起,方便到期时删除。另外,在retensionpolicy下,进一步细分了retensionpolicy的保留期,将每个时间段的数据存储在一个shardgroup中,这样当某个segment的shardgroup到期时,其删除整个文件避免从存储引擎中提取一些数据。例如,数据库下的数据可能会保留30天或7天,它们会根据不同的保留策略进行存储。假设7天的数据会继续分成1天,分别存储在7个分片组中。当第8天的数据产生时,会新建一个shardgroup并写入,写入第1天的shardgroup。删除整个东西。至此,在相同的保留策略下,当前发送的时序数据只会落在当前时间段内,即只有***分片组有数据写入。为了增加并发量,将一个分片组分成多个分片。这些分片是全球唯一的,分布在所有物理节点上。每个分片对应一个负责存储数据的tsm存储引擎。在请求访问数据时,可以通过请求的信息锁定某个数据库和保留策略,然后根据请求中的时间段信息锁定某个(某些)分片组。对于写入,每条写入的数据对应一个serieskey(这个概念后面会介绍),通过对serieskey进行hash取模,可以锁定一个shard进行写入。shard是有副本的,在写入的时候,会使用no-owner-multiple-write的策略,对每个副本同时写入。查询时,由于查询请求中没有serieskey信息,所以分片组中的所有分片只能查询一次。对于分片,会在其副本中选择一个可用的物理节点进行访问。那么一个分片组应该有多少个分片呢?为了在不过多打乱数据整体顺序的情况下达到最大并发,物理节点数和副本数确定后,一个分片组的分片数除以机器数与数ofcopies,保证当前数据可以均匀写入所有物理节点,查询效率不会因为分片过多而受到影响。比如图中数据集群有6个物理节点,用户指定双副本,所以有3个分片。2.集群结构整个系统分为三个部分:代理、元集群和数据集群。代理负责接收请求,是无状态的。可以连接lvs,支持横向扩展。元集群保存了上述逻辑存储层次及其与物理节点的对应关系,通过raft协议保证元数据的强一致性。这里元信息存储在内存中,日志和快照持久化到磁盘。数据集群是一个真正的数据存储节点,数据以分片为单位存储在该节点上,每个分片对应一个tsm存储引擎。当请求到来时,一个代理被lvs锁定,代理首先根据数据库、保留策略和时间段在元集群中搜索元信息,最后得到一个从分片到物理节点的映射,然后转换映射关系到物理节点到分片的映射返回给代理。***根据这个映射关系,访问数据集群指定的物理节点中的具体分片。至于shard下的数据访问,后面会介绍。三、数据访问1、语法格式Influxdb查询提供了一种类似于关系型数据库的查询方式,类似于关系表的显示方式:measurement,时序数据库时间被视为一个永恒的列,其他列分为两类:field:一种是field,它是时序数据中最关键的数据部分,它的值会随着时间的流逝不断增加,比如每个时间点两台机器之间的延迟。tag:另一种是tag,是一个字段值的一些标签,所以都是字符串类型,取值范围非常有限。比如某个时间点的delay字段值为2ms,对应两个标签属性,从哪台机器到哪台机器的延迟,所以可以设计两个标签:from和to。measurement显示的第一行是key,其余的可以看成是value。这样标签就有了tagkey,tagvalue,字段就有了fieldkey,fieldvalue。2.数据读写当接收到一行数据时,会转换成如下格式:measurement+tagkey1+tagvalue1+tagkey2+tagvalue2+fieldkey+fieldvalue+time如果一行有多个字段,则像这样的数据存储分成多块。influxdb的存储引擎可以理解为一个映射,从measurement到fieldkey作为存储key,后面的fieldvalue和time就是存储值,这些值会不断的增加。在存储引擎中,这些值会作为一列存储在一起,因为是随时间变化的数据,将它们保存在一起可以提高压缩效果。另外,storagekey去掉fieldkey后剩下的部分就是上面说的serieskey。上面提到了访问请求是如何锁定集群中的分片的,这里介绍分片内访问。influxdb的查询类似于SQL语法,但是与SQL语句相关的零散信息无法直接查询存储引擎,因此需要一些策略将SQL语句转化为存储键。InfluxDB通过建立倒排索引将where之后的标签信息转化为所有相关serieskey的集合,然后将每个serieskey与select后面的fieldkey拼接形成存储key,从而可以按列检索对应的数据。通过对tsm存储引擎中storagekey中serieskey的解析,可以构建倒排索引。新版influxdb将倒排索引持久化到每个shard中,对应存储数据的tsm存储引擎,称为tsi存储引擎。倒排索引相当于一个三层地图。地图的key是measurment,value是一个二层地图。这个二层图的key是tagkey,对应的value是一层图。这个单层map的valuekey是tagval,对应的value是serieskeys的集合。此集合中的每个serieskey字符串都包含地图索引路径上的测量值、tagkey和tagval。这样就可以分析查询SQL,利用from之后的measurement查询倒排索引的三级map得到二级map,然后在where之后分析多个过滤逻辑单元。以tagkey1=tagval1为例,将这两个信息作为第二层map的key,找到最终的值:serieskeys的集合,这个集合中的每个serieskey字符串包含measurment,tagkey1和tagval1,它们是serieskeys满足当前过滤逻辑单元。根据这些逻辑单元的AND或逻辑,合并对应的serieskey集合,最后根据SQL的语义过滤掉所有逻辑serieskey集合,再将这些serieskey与select后面的fieldkey拼接。得到最终的存储密钥后,就可以读取数据了。不带聚合功能的查询:如图所示,对于一个serieskey,需要将多个fieldkey拼接起来,然后取出多列的数据。他们出来之后,面临的问题是如何把数据合并成一行。Influxdb的行列约束比较宽松,不能简单的用列内的偏移量来决定行。Influxdb以serieskey和time作为判断列数据为一行的依据。每个serieskey对应的多个column聚合成一个多行为粒度的数据流,多个serieskey对应的数据流按照一定的顺序聚合成一个数据流,作为最终的结果集返回给客户端。带有聚合函数的查询:这种方式与上面的查询正好相反。这里针对的是聚合函数的参数字段,拼接了很多serieskey。当然,最终的目的都是一样的,都拿到了存储密钥。多个存储键可以读取多个数据流,这些数据流面临两种处理。首先将它们按照一定的顺序汇聚成一个数据流,然后按照一定的策略划定数据流中的一些相邻数据进行聚合计算,得到最终的聚合值。.这里的顺序和策略来自SQL语句中groupby之后的聚合方式。多数据流的合并聚合方式同样适用于分片上的查询结果。写起来比较简单,直接更新数据存储引擎和倒排索引即可。3、全过程上面已经讲了访问的全过程。这里做一个大概的概述:分为两个阶段,分片上面的查询,分片下面的查询。首先通过lvs将访问请求锁定到某个proxy,由proxy在meta集群中查找meta信息。根据请求信息,锁定数据库、retentionpolicy、shardgroup,然后得到很多shard。对于写入操作,根据写入时的serieskey,锁定一个shard进行写入。由于分片有多个副本,因此需要同时向多个副本写入数据。对于查询来说,serieskey是无法通过请求信息获取到的,所以需要查询所有的分片,并为每个分片选择一个可用的副本进行访问。经过上面的处理,得到了shard到物理节点的映射,然后逆向为物理节点到shard的映射,返回给proxy,proxy就可以访问某个节点上对应的shard的数据集群。对于shard下的写访问,需要将insert语句拆开,组合成存储键值对存储在tsm存储引擎中,然后根据组合后的serieskey更新倒排索引。查询分片下的访问,分析SQL语句,查询倒排索引,获取其相关的serieskey集合,拼接成字段,形成数据访问的最终存储key。然后很多数据在数据节点上的分片上合并聚合,在代理上的数据上合并聚合。最后,代理将访问结果返回给客户端。四、排错1、上面提到的influxdb对shards提供复制容错的策略。当写入的数据发送给代理时,代理将数据以无所有者的多次写入的形式发送给所有分片副本。meta集群以心跳的形式监控数据节点是否在线,读取时在在线数据节点中随机选择一个读取节点对同一个shard进行读取。如果写入时某个数据节点不可用,则将其写入代理的临时文件中,待网络恢复正常后将临时数据发送至指定节点。2.处理(1)数据集群扩容新节点加入数据集群时,目前不支持已有数据的自动迁移,但是已经做了一些努力,让当前写入的数据尽快应用到新节点上可能的。当有新的节点加入时,会以当前时间作为当前分片组的结束时间,然后根据新的数据节点数创建新的分片组,使当前数据量均匀分布立即分发到每个数据节点,每个分片组相关的元信息存储在元集群中,因此不会干扰之前数据的读取。(2)数据节点暂时不可用如果数据节点处于短期不可用状态,包括短期网络故障后的自我恢复,或者运维人员在硬件故障后介入,最后数据节点下线前还有数据,那么可以用原来的身份加入数据集群。对于写入,proxy会在不可用期间临时存储datanode的数据,并在数据加入集群时将这部分数据再次发送给datanode,以保证数据的最终一致性。(3)数据节点长时间不可用。如果数据节点由于某种原因不能或者不需要以原来的身份加入集群,需要运维人员手动将不可用的数据节点下线,那么当本机可用时,可以加入集群作为一个全新的数据,相当于集群的扩容。5.总结QTSDB集群实现如下:写入时根据serieskey将数据写入指定的shard,但读取时无法预测serieskey,因此需要查询每个shard。将整个读取过程分为两个阶段:读取数据节点上的存储引擎并合并聚合节点内部的多个分片,在代理节点上聚合多个数据节点的数据,进行后期的合并聚合,形成最终的结果集返回给客户端。QTSDB现有的集群功能还存在不完善的地方,将在以后的使用中不断完善。【本文为栏目组织360科技、微信公众号《360科技(id:qihoo_tech)》原创文章】点此查看本作者更多好文

猜你喜欢