HBase Schema

数据表架构

所有 OpenTSDB 数据点都存储在单个大规模表中,默认情况下命名为tsdb。这是为了利用 HBase 的排序和区域分布。所有值都存储在t列系列中。

行键 -行键是字节数组,由可选的盐,度量 UID,基本时间戳和 tagk/v 对[salt]<metric_uid><timestamp><tagk1><tagv1>[...<tagkN><tagvN>]组成。默认情况下,UID 编码为 3 个字节。

启用盐化功能(从 OpenTSDB 2.2 开始),第一个字节(或多个字节)是哈希盐 ID,以更好地在多个区域和/或区域服务器之间分配数据。

时间戳是 Unix 纪元值,以秒为单位,编码为 4 个字节。各行分为小时增量,由每行中的时间戳反映。因此,每个时间戳将被归一化为小时值,例如* 2013-01-01 08:00:00 *。这是为了避免在一行中填充太多数据点,因为这会影响区域分布。同样,由于 HBase 在行键上进行排序,因此,具有相同度量和时间段但具有不同标签的数据将被分组在一起以进行有效的查询。

一些未加盐的行键示例,表示为十六进制:

00000150E22700000001000001
00000150E22700000001000001000002000004
00000150E22700000001000002
00000150E22700000001000003
00000150E23510000001000001
00000150E23510000001000001000002000004
00000150E23510000001000002
00000150E23510000001000003
00000150E24320000001000001
00000150E24320000001000001000002000004
00000150E24320000001000002
00000150E24320000001000003

where:

00000150E22700000001000001
'----''------''----''----'
metric  time   tagk  tagv

这表示一个 Metrics,但三个小时内有四个时间序列。请注意,如何在一个时间序列中包含两组标记:

00000150E22700000001000001000002000004
'----''------''----''----''----''----'
metric  time   tagk  tagv  tagk  tagv

标签名称(tagk)在存储之前按字母 Sequences 排序,因此“主机”标签将始终在行密钥/ TSUID 中排在“所有者”之前。

数据点列

到目前为止,最常见的列是数据点。这些是将数据发送到 TSD 进行存储时记录的实际值。

列限定符 -限定符由 2 或 4 个字节组成,这些字节编码与行的基本时间的偏移量以及用于确定该值是整数还是十进制值的标志。限定符对行基准时间的偏移量以及所存储数据的格式和长度进行编码。

具有 2 个字节限定符的列具有以秒为单位的偏移量。限定词的前 12 位表示一个整数,该整数是与行键中的时间戳相比的一个增量。例如,如果将行键标准化为1292148000,并且数据点 Importing1292148123,则记录的增量为123。后 4 位是格式标志

具有 4 个字节限定符的列的偏移量以毫秒为单位。限定符的前 4 位将始终设置为十六进制的1F。接下来的 22 位将偏移量(以毫秒为单位)编码为无符号整数。接下来的 2 位被保留,最后的 4 位是格式标志。

任一列类型的最后 4 位描述存储的数据。第一位是标志,指示该值是整数还是浮点数。值 0 表示整数,值 1 表示浮点数。后 3 位表示数据长度,偏移 1.值000表示 1 字节,而010表示 2 字节。该长度必须反映一个 1、2、4 或 8 的值。其他任何内容都表示错误。

例如,0100表示列值是一个 8 字节的带符号整数。 1011表示列值是 4 字节浮点值,因此1292148123处数据点的限定符(整数值为 4294967296)将具有000001111011010007B4的限定符(十六进制)。

列值 -1 至 8 个字节,如限定符标志所示进行编码。

Compactions

如果已为 TSD 启用了压缩,则行的基准小时过后或对该行进行查询后,可以对行进行压缩。压缩列只是将所有数据点压缩在一起,以减少不同数据点消耗的开销。首先将数据写入各个列以提高速度,然后将其压缩以提高存储效率。压缩一行后,将删除单个数据点。数据可以写回到该行,稍后再进行压缩。

Note

OpenTSDB 压缩过程的范围和定义与 HBase 的压缩思想完全不同。

列限定符 -压缩列的限定符将始终是偶数个字节,并且仅仅是行中每个数据点的限定符的串联。由于我们知道每个数据点限定符为 2 个字节,因此将其拆分很简单。具有两个数据点的十六进制限定符可能看起来像07B407D4

列值 -该值也是所有单个数据点的串联。限定符首先被拆分,每个数据点的标志确定解析器消耗 4 字节还是 8 字节

Comments 或其他对象

一行可以存储有关与数据点内联的时间序列的 Comments。对象与数据点的区别在于限定符中的字节数为奇数。

列限定符 -限定符位于 3 或 5 个字节上,第一个字节为 ID,该 ID 将列表示为限定符。对于 Comments,第一个字节的十六进制值始终为0x01(Future 的对象类型将具有不同的前缀)。其余字节以与数据点类似的方式对行基础时间的时间戳增量进行编码,尽管没有标志。如果限定符的长度为 3 个字节,则偏移量以秒为单位。如果限定符的长度为 5 个字节,则偏移量为毫秒。因此,如果我们在1292148123处记录 Comments,则增量将为123,而以十六进制表示的限定符将为01007B

列值 -Comments 值是 UTF-8 编码的 JSON 对象。不要直接修改此值。字段的 Sequences 很重要,这会影响 CAS 调用。

附加数据点

OpenTSDB 2.2 引入了使用append方法而非常规put方法将数字数据点写入 OpenTSDB 的想法。通过在一行中的一行中写入所有数据,这节省了 HBase 的空间,从而实现了 TSD 压缩的好处,同时避免了将大量数据读回到 TSD 并将它们重新写入 HBase 的问题。缺点是该架构与常规数据点不兼容,并且在 HBase 区域服务器上对每个值执行读取,修改,写入操作时,它们需要使用更多的 CPU。

行键 -与常规值相同。

列限定符 -限定符始终是对象前缀0x05,与基准时间相比在两个字节上的偏移量为 0.例如。 0x050000

列值 -每个列值是原始数据点限定符偏移量和格式为<offset1><value1><offset2><value2>...<offsetN><valueN>的值的串联。值可以按任何 Sequences 出现,并在查询时进行排序(可以选择将排序后的结果重新写回 HBase)。

UID 表架构

一个单独的较小的表tsdb-uid存储正向和反向 UIDMap。存在两列,一列名为name将 UIDMap 到字符串,另一列idMap 字符串到 UID。列族中的每一行将至少具有 Map 值的三列之一。标准的列限定符为:

  • metrics用于将度量标准名称 Map 到 UID

  • tagk用于将标签名称 Map 到 UID

  • tagv用于将标签值 Map 到 UID。

name系列还可以包含其他元数据列(如果已配置)。

id 列族

行键 -这将是分配给 UID 的字符串。例如。对于 Metrics,我们的值可以为sys.cpu.user,对于标签值则可以为42

列限定符 -上面的标准列类型之一。

列值 -默认情况下以 3 个字节编码的无符号整数,反映为列类型分配给字符串的 UID。如果在源代码中更改了 UID 长度,则宽度可能会有所不同。

名称列族

行键 -默认情况下,无符号整数 UID 编码为 3 个字节。如果源代码中的 UID 长度已更改,则宽度可能会有所不同。

列限定符 -上面的标准列类型之一,或者metrics_metatagk_metatagv_meta之一。

列值 -对于上面的标准限定符,是分配给 UID 的字符串。对于*_meta列,该值将是 UTF-8 编码的 JSON 格式的 UIDMeta 对象,为字符串。不要在 OpenTSDB 外部修改列值。字段的 Sequences 很重要,这会影响 CAS 调用。

UID 分配行

id列族中的一行是单字节键\x00。分配新的 UID 时,这是针对正确的列类型(Metrics,tagk 或 tagv)递增的 UID 行。列值是 8 个字节的带符号整数,反映了为每种类型分配的最大 UID。分配后,OpenTSDB 在适当的列上调用 HBase 的原子增量命令以获取新的 UID。

元表架构

该表是 OpenTSDB 中存储的不同时间序列的索引,并且可以包含每个序列的元数据以及每个序列存储的数据点数。请注意,只有在 OpenTSDB 已配置为跟踪元数据或用户通过 API 创建 TSMeta 对象时,数据才会写入该表。仅使用一个列族,即name族,当前有两种类型的列,即 meta 列和 counter 列。

Row Key

这与不带时间戳的数据点表行键相同。例如。 <metric_uid><tagk1><tagv1>[...<tagkN><tagvN>]。所有列类型都共享它。

TSMeta Column

这些列存储类似于 UIDMeta 对象的 UTF-8 编码,JSON 格式的对象。限定词始终为ts_meta。不要在 OpenTSDB 外部修改这些列值,否则可能会中断 CAS 调用。

Counter Column

这些列是原子增量器,用于计数时间序列中存储的数据点的数量。限定符为ts_counter,值为 8 字节带符号整数。

树表架构

该表充当索引,将时间序列组织成类似于文件系统的分层结构,以供 Graphite 或其他仪表板之类的工具使用。一棵树由一组规则定义,这些规则处理 TSMeta 对象,以确定在层次结构中的某个位置(如果有的话)应该出现一个时间序列。

每棵树都分配有唯一 ID,该 ID 由第一棵树的以1开头的无符号整数组成。与树相关的所有行均以该 ID 编码为两个字节的数组作为前缀。例如。 \x00\x01代表 UID 1

Row Key

树定义行用两个字节的树 ID 进行键控。与树定义有关的列以及根分支出现在此行中。定义由用户生成。

可能包括两个特殊行。它们分别在collisions行的<tree ID>\x01not matched行的<tree ID>\x02上键入。这些是在树处理期间生成的,稍后将进行描述。

其余的行是包含有关层次结构信息的分支行和叶子行。这些行在<tree ID><branch ID>上键入,其中branch ID是分支显示名称的哈希值的串联。例如,如果我们有一个扁平化的分支dal.web01.myapp.bytes_sent,其中每个分支名称都用句点分隔,则将有 3 个分支级别。 dalweb01myapp。叶子将被命名为bytes_sent并链接到 TSUID。在 Java 中散列每个分支名称将返回一个 4 字节的整数,并转换为十六进制以提高可读性:

  • dal = x00x01x83x8F

  • web01 = x06xBCx4Cx55

  • myapp = x06x38x7CxF5

如果此分支属于树1,则dal的行键将为\x00\x01\x00\x01\x83\x8Fmyapp的分支将是\x00\x01\x00\x01\x83\x8F\x06\xBC\x4C\x55\x06\x38\x7C\xF5。通过使用行前缀过滤器(包括树 ID 和当前分支级别)和通配符来匹配任意数量的子分支级别(通常仅向下一个级别),该模式允许通过提供行键过滤器进行导航。

Tree Column

一棵树在树行的tree列中定义为 UTF-8 编码的 JSON 对象(由树的 ID 标识)。该对象包含用于通过树处理时间序列的描述和配置设置。不要在 OpenTSDB 之外修改此对象,因为它可能会中断 CAS 调用。

Rule Column

在树行中,有 0 个或更多规则列,它们定义时间序列上的特定处理任务。这些列也是 UTF-8 编码的 JSON 对象,并通过 CAS 调用进行了修改。格式为rule:<level>:<order>的限定符 id,其中<level>是集合中规则的主要处理 Sequences(从 0 开始),order是给定级别内规则的处理 Sequences(从 0 开始)。例如rule:1:0定义级别 1 和 Sequences0 的规则。

树碰撞栏

如果为树启用了碰撞存储,则会为每个时间序列记录一列,而该时间序列会创建已经为先前时间序列创建的叶子。这些列用于调试规则集,仅在树的冲突行中显示。限定符的格式为tree_collision:<tsuid>,其中 TSUID 是表示时间序列标识符的字节数组。这允许进行简单的getRequest调用,以确定是否由于碰撞而没有在树中显示特定的时间序列。 colission 列的值是记录为叶子的 TSUID 的字节数组。

不匹配的列

与冲突类似,当为树启用时,可以为每个时间序列记录一列,这些列与规则集中的任何规则都不匹配,因此没有出现在树中。这些列仅出现在树的不匹配行中。限定符的格式为tree_not_matched:<TSUID>,其中 TSUID 是表示时间序列标识符的字节数组。不匹配的列的值是未能匹配规则的 TSUID 的字节数组。

Branch Column

分支列的限定符为branch,并且包含描述当前分支和可能存在的任何子分支的 UTF-8 JSON 编码对象。分支列可能出现在除冲突列或不匹配列之外的任何行中。树定义行中的分支是root分支,并链接到子分支的第一级。这些链接用于遍历层次结构。

Leaf Column

叶子是到特定时间序列的 Map,代表层次结构的结尾。叶列的限定符格式为leaf:<TSUID>,其中 TUID 是表示时间序列标识符的字节数组。叶子的值是描述叶子的 UTF-8 编码的 JSON 对象。叶子可能会出现在碰撞以外的任何行中或不匹配的行中。

汇总表架构

从 OpenTSDB 2.4 开始,汇总表和预聚合表的概念。尽管 TSDB 可以根据需要长时间存储原始值,但是在大量原始数据中查询宽泛的时间 Span 会减慢对爬网的查询速度,并可能使 JVM 处于 OOM 状态。取而代之的是,各个时间序列可以按时间汇总(或下采样),并存储为单独的值,以便以较低的分辨率扫描更宽的时间 Span。此外,对于具有高基数的 Metrics,可以存储预聚合组以显着提高查询速度。

汇总数据有三种类型:

  • 汇总-这是单个时间序列跨时间的下采样值。这类似于在查询中使用降采样器,在该查询中,时间序列可能每分钟都有一个数据点,但是使用sum聚合每小时会降采样到一个数据点。在这种情况下,结果汇总值是 60 个值的总和。例如。如果每个 1 分钟数据点的值为1,则汇总结果将为60

  • 预聚合-对于具有高基数(许多唯一标签值)的度量,扫描所有序列可能会很昂贵。以system.interface.bytes.outMetrics 为例,其中 10,000 个主机分布在 5 个数据中心中。如果用户经常查看数据中心输出的总数据(查询看起来类似于 aggregate = sum 和 data_center = *),则预先计算每个数据中心的总和将导致每个时间段从存储中获取 5 个数据点而不是 10K。所得的预聚合将具有与原始时间序列不同的标记集。在上面的示例中,每个系列都可能具有host标签和data_center标签。预聚集后,将删除host标签,仅保留data_center标签。

  • 汇总的预汇总-与原始时间序列类似,也可以按时间汇总预汇总的数据。这样可以在较长时间内跨预聚合数据提高查询速度。

Configuration

TODO -解决配置问题。汇总配置由表名称,间隔范围和汇总间隔组成。原始的预聚合也可以存储在数据表或汇总表中。

Pre-Aggregate Schema

在 OpenTSDB 的实现中,启用汇总功能后,会将新的用户可配置标签添加到所有时间序列。默认密钥是_aggegate,其值为raw或一个聚合函数。标签用于区分预汇总的数据和原始(原始)值。因此,预聚合的数据以与原始时间序列相同的方式存储,并且可以写入原始数据表或存储在单独的表中,以提高查询性能。

Rollup Schema

汇总的数据必须与原始数据存储在单独的表中,以避免现有的架构冲突并允许执行更高效的查询。

行键 -汇总的行键与原始数据表的格式相同。

列限定符 -汇总数据的列不同,由<aggregation_function>:<time offset><type + length>组成,其中聚合函数是大写字符串,由用于生成汇总的函数名称组成,时间偏移量是与行基准时间的偏移量,类型长度描述列值编码。

  • 聚合函数-这是诸如SUMCOUNTMAXMIN之类的函数的名称。

  • 时间偏移量-这是基于汇总表配置的偏移量,通常为 2 个字节。偏移量不是距底数的特定秒数或分钟数,而是偏移量间隔的索引。例如,如果将表配置为以每行 1 小时的分辨率存储 1 天的数据,则行键的基本时间戳将与每日边界对齐(在 Unix 纪元时间戳上)。那么该行可能有 24 个偏移量(一天中每小时 1 个偏移量)。给定日期午夜的数据点的偏移量为 0,而 23:00 小时值的偏移量为 22.由于汇总时间戳记与时间边界对齐,因此限定符可以节省大量空间。

  • 类型和长度-与原始数据表类似,每个偏移字节数组的后 4 位包含数据值的编码,包括其长度以及是否为浮点值。

每天 1 小时间隔表的列限定符示例如下:

SUM:0010
'-''---'
agg offset

聚集器为SUM时,偏移量为1且长度为整数值的 1 个字节。

列值 -值与主数据表中的值相同。

TODO -需要做的进一步工作:

  • 压缩/附加-当前模式不支持压缩或附加数据类型。这些可以通过表示每个聚合函数的单个列(例如SUMCOUNT)并将偏移量和值存储在类似于主数据表的列值中来实现。

  • 其他数据类型-当前,只有数字数据点被写入预汇总和汇总表。我们需要支持汇总 Comments 和其他类型的数据。