作者简介
运小尧 百度高级研发工程师
一、简介
基本功能方面,我们的 TSDB 在数据的收集上提供了 HTTP、Thrift 等 API;对查询,除了提供 API 之外还提供了命令行工具(CLI Tool),这些基本功能的设计在不同的 TSDB 中大同小异,因此本文不再赘述。
由于数据规模庞大且出于业务数据隔离和定期清理的需要,我们设计了分库分表功能;为了提升历史数据存储和查询效率,同时节省存储成本,我们又设计了多级降采样功能。这些针对性的功能设计为支撑万亿级数据写入和高并发查询打下了坚实基础。
二、分库分表
谈及大规模数据库的性能优化,分库分表是一个绕不开的思路,设计得当可以大幅缓解数据库的压力和性能瓶颈,那么分库分表能帮我们解决什么问题?如何解决?
回顾前文提及的挑战,我们的系统需要满足业务数据需要隔离保存策略的个性化需求,在单表结构中实现这样的需求相对复杂,维护成本也较高。通过“垂直分库”,可以将不同业务的数据存储在不同的数据库中,每个数据库可以应用不同的数据保存策略(图 1)。
图1 垂直分库
另一方面,由于数据规模增长迅速,单表结构使得在 HBase 执行 Compaction 时会产生可观的 I/O 负载,以至于拖累整个系统的性能。通过“水平分表”,按照某种规则将原本较大的数据表中的数据划分到相同结构的较小的表中,如图2,可以一定程度上减小 Compaction 时的 I/O 负载。
图2 水平分表
1 基于HBase的“垂直分库”
在监控系统中我们使用“Product”这个字段作为业务的唯一标识(“Product”相当于租户),并为不同的“Product”建立逻辑上的“Database”,每一个“Database”包含一套 TSDB 所需的完整的表(包含前文提到的数据表和维度索引表),见图 3。之所以从逻辑上建立数据库的概念,是为了同具体的存储实现解耦,以便在不同的底层存储系统上都能够实现这个机制。
在 HBase 0.9x 版本中尚没有类似“Database”的概念,可以通过约定表的命名规则来实现,比如以前缀来标识不同的“Database”的数据表 ${Product}-data,在数据写入 HBase 之前,根据“Product”字段拼接出对应的表名即可:
图3 按产品线分库
为不同分库对应的数据表设置不同的 TTL,便实现了差异化的数据保存策略。
2 基于HBase的“水平分表”
根据监控时序数据按时间顺序以稳定频率产生的特点可以知道,相同长度的时间内产生的数据量是近似相等的,据此我们能够轻易地将数据按照时间相对均匀地划分到不同的时间窗口内。随着时间的推移,旧的时间窗口内的数据表逐渐被“冷落”,不再承载写入请求,只承载较低频率的访问请求,新的时间窗口内数据表将接力顶替(图 4)。
图4 按时间分表
分表只在某个“Database”内部进行,且只对数据表有效。每个分表是一个“Slice”,同分库一样,在 HBase 中按时间分表也可以通过约定表的命名规则来实现,我们以表内数据的起始时间 startTime 作为表名后缀 ${Product}-data-${startTime},相邻分表之间的 startTime 间隔固定的长度,比如 86400 秒(1 天):
Database1:
Slice1: product1-data-1510000000
Slice2: product1-data-1510086400
...
Database2:
Slice1: product2-data-1510000000
Slice2: product2-data-1510086400
...
“水平分表”带来的另外一个好处是可以方便地实现数据批量清除(即 TTL 的功能),假如某个“Database”的数据保存一年,那么到期时可以根据 startTime 直接删除其中一年以前的 Slice,干净利落。
三、多级降采样
业务对于监控数据的使用需求多种多样,有查最新数据的异常告警,也有查看一整年指标数据的趋势图展示,数据量越大查询耗时就越久,如果放在浏览器端处理也要耗费大量的内存。这不但对系统造成了很大的压力,也给用户带来了难以忍受的查询体验。鉴于此,我们引入了多级的降采样机制来应对不同跨度的数据查询。
降采样(Downsampling)在此是指对时序数据进行降频,将原本较细粒度的数据降频后得到较粗粒度的数据。这样一来可以成倍减少数据规模,使得粗粒度的数据能够以更小的成本保存更长时间。
降采样后的数据点只保留原始数据的一些统计特征,我们保留了四个统计特征,由四个统计特征取值共同构成一个统计值StatisticsValue
* max:采样周期内样本值的最大值
* min:采样周期内样本值的最小值
*sum:采样周期内样本值的和值
*count:采样周期内样本数
可见降采样会造成原始数据失真,而不同场景对数据失真的容忍程度不同,在查询一年趋势的场景下,数据只需要大致的趋势正确即可;但在异常检测等一些场景中,则需要细粒度的原始数据来提供更多的信息。
1 预降采样(Pre-downsampling)
预降采样是在数据写入之前就按照预定的周期(Cycle)对其进行降采样,采样后的数据与原始数据同时保留。
在预降采样时将采样固定分为两个等级,包括 Level 1 降采样和 Level 2 降采样,每一级将上一级的数据按照更大的周期求出一个统计值(图 5)。
图5 两级预降采样
预降采样与数据写入并行进行,降采样后的数据定期写入对应表中,不同级别的采样表使用不同粒度的分表策略,表的数据保存时长(TTL)随着降采样级别递增,见图 5(略去了分表的细节)(图 6)。
图6 原始数据表与不同级别的降采样表
2 后降采样(Post-downsampling)
后降采样则是指在查询数据时,实时地根据用户指定的查询周期(Period)对查询结果数据进行降采样,与预降采样原理类似且更为灵活,可以按照任意周期进行降采样,但后降采样的结果不会被写回存储。
后降采样通常与预降采样配合使用,可以高效地完成一些原本困难的查询任务。例如,在查询一年的数据趋势图时,可以直接拉取 Level 2 降采样的数据,使得查询结果的数据量级降低数百倍,查询的响应时间也成倍地减少。
总结
本篇为大家介绍了我们 TSDB 中两个重要的功能:分库分表和多级降采样,这使我们从功能的设计上消除了在大规模场景下系统遇到的一些性能瓶颈。在下一篇文章中,我们将介绍 TSDB 在架构层面如何设计,以提升系统的性能和保障系统可用性。