OpenTSDB在HBase中的底层数据结构设计

Stella981
• 阅读 812

0.时序数据库

时间序列(Time Series):是一组按照时间发生先后顺序进行排列的数据点序列,通常一组时间序列的时间间隔为一恒定值(如1秒,5分钟,1小时等)。

时间序列数据可被简称为时序数据。实时监控系统所收集的监控指标数据,通常就是时序数据 。时序数据具有如下特点:

  • 每一个时间序列通常为某一固定类型的数值
  • 数据按一定的时间间隔持续产生,每条数据拥有自己的时间戳信息
  • 通常只会不断的写入新的数据,几乎不会有更新、删除的场景
  • 在读取上,也往往倾向于读取最近写入的数据。

OpenTSDB是其中一种时序数据库实现,是一种基于Hbase、分布式、可伸缩的时间序列数据库。

1.OpenTSDB基本概念

  • metric:指标名,这个就是我们监控的指标,比如 sys.cpu.user; 
  • timestamp:时间戳,监控数据产生的时间; 
  • value:监控值,Long 或者 Double 类型的数据,这个是监控指标在某个时间的具体值; 
  • tag:标签,包括标签名字(tagk)和标签值(tagv),比如 tagk1=tagv1,主要用于描述数据属性,每条时序数据必须包含一组和多组的标签数据。目前 OpenTSDB 最多支持8组标签。

 

2.数据样例:

sys.cpu.user host=webserver01 1356998400 50
sys.cpu.user host=webserver01,cpu=0 1356998400 1
sys.cpu.user host=webserver01,cpu=1 1356998400 0
sys.cpu.user host=webserver01,cpu=2 1356998400 2
sys.cpu.user host=webserver01,cpu=3 1356998400 0
............
sys.cpu.user host=webserver01,cpu=63 1356998400 1

其中,每一行表示时间序列中的一个DataPoint,每部分对应如下:

OpenTSDB在HBase中的底层数据结构设计

 每一个Data Point,都关联一个metrics名称,但可能关联多组<tagKey,tagValue>信息。而关于时间序列,事实上就是具有相同的metrics名称以及相同的<tagKey,tagValue>组信息的Data Points的集合。

3.存储模型

OpenTSDB是基于HBase进行数据存储,在HBase中存放tsdb、tsdb-meta、tsdb-tree、tsdb-uid四张表,主要用到的是tsdb、tsdb-uid两个。

1)tsdb-uid

为了统一各个值的长度以及节省空间,OpenTSDB中为每一个metrics名、tagKey以及tagValue都定义了一个唯一的数字类型的标识码(Unique Identifier, UID),这些UID信息被保存在OpenTSDB的元数据表tsdb-uid中。同时,为了从UID索引到metrics(或tagKey、tagValue),同时也要从metrics(或tagKey、tagValue)索引到UID,OpenTSDB同时保存了这两种映射关系数据

列族Column Family:

在元数据表中,把这两种数据分别保存到两个名为**"id""name"**的列族(Column Family)中,Column Family描述信息如下所示:

{NAME => 'id', BLOOMFILTER => 'ROW', COMPRESSION => 'SNAPPY'}
{NAME =>'name',BLOOMFILTER => 'ROW', COMPRESSION => 'SNAPPY', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'}

OpenTSDB分配UID时遵循如下规则:

  • metrics、tagKey和tagValue的UID分别独立分配
  • 每个metrics名称(或tagKey/tagValue)的UID值都是唯一。不存在不同的metrics(或tagKey/tagValue)使用相同的UID,也不存在同一个metrics(或tagKey/tagValue)使用多个不同的UID
  • UID值使用三个字节进行存储,其范围是0x000000到0xFFFFFF。

关于metrics名为"cpu.hum",tagKey值为"host",tagValue值分别为"189.120.205.26"、"189.120.205.27"的UID信息定义如下:

OpenTSDB在HBase中的底层数据结构设计

 说明:

  • RowKey为"0"的行中,分别保存了metrics、tagKey和tagValue的当前UID的最大值。当为新的metrics、tagKey和tagValue分配了新的UID后,会更新对应的最大值
  • RowKey为"1"的行中,RowKey为UID,Qualifier(列名)为"id:metrics"的值"metrics",Qualifier为"id:tagk"的值为tagKey,Qualifier为id:tagv的值为tagValue
  • RowKey为"2"的行中,RowKey为UID,Qualifier为"id:tagv"的值为tagValue,暂不存在UID为"2"的metrics和tagKey
  • RowKey为"189.120.205.26"的行中,Qualifer为"name:tagv"的值为UID。表示当"189.120.205.26"为tagValue时,其UID为1
  • RowKey为"189.120.205.27"的行中,Qualifer为"name:tagv"的值为UID。表示当"189.120.205.26"为tagValue时,其UID为2
  • RowKey为"cpu.hum"的行中,Qualifer为"name:metrics"的值为UID。表示当cpu.hum为metrics时,其UID为1
  • RowKey为"host"的行中,Qualifer为"name:tagk"的值为UID。表示当host为tagValue时,其UID为1

由于HBase的存储数据类型是Bytes,所以UID在存储时会被转换为3个字节长度的Bytes数组进行存储。

TUID:

对每一个Data Point,metrics、timestamp、tagKey和tagValue都是必要的构成元素。除timestamp外,metrics、tagKey和tagValue的UID就可组成一个TSUID,每一个TSUID关联一个时间序列,如下所示:

<metrics_UID><tagKey1_UID><tagValue1_UID>[...<tagKeyN_UID><tagValueN_UID>]

在上面的例子中就涉及两个TSUID,分别是:

OpenTSDB在HBase中的底层数据结构设计

 2)tsdb

OpenTSDB在HBase中的底层数据结构设计

tsdb为存放OpenTSDB中所有数据记录的表,下面具体介绍tsdb的结构设计。

① RowKey设计

tsdb的HBase RowKey中包含主要组成部分为:盐值(Salt)、metrics名称、时间戳、tagKey、tagValue等部分。

在tsdb-uid中提到,为了统一各个值的长度以及节省空间,对metrics名称、tagKey和tagValue分配了UID信息。所以,在HBase RowKey中实际写入的是metrics UID、tagKey UID和tagValue UID(存放在tsdb-uid中)。

HBase RowKey的数据模型如下图所示:

OpenTSDB在HBase中的底层数据结构设计

  • SALT:建议开启SALT功能,可以有效提高性能。SALT数据的长度是变长的:如果SALT的值值少于256,那么只用一个字节表示即可;如果需要设置更大的SALT值,也会相应地占用更多的空间。
  • Metric ID:metrics名经过编码后,每个Metric ID的长度为三个字节。
  • Timestamp:这里整点小时级别的时间戳
  • tagKey UID & tagValue UID:tagKey和tagValue经过编码后,每个tagKey UID和tagValue UID的长度都为三个字节。tagKey UID和tagValue UID必须成对出现,最少必须存在1对,最多存在8对。

② Qualifier设计

Qualifier用于保存一个或多个DataPoint中的时间戳、数据类型、数据长度等信息。

由于时间戳中的小时级别的信息已经保存在RowKey中了,所以Qualifier只需要保存一个小时中具体某秒或某毫秒的信息即可,这样可以减少数据占用的空间。

一个小时中的某一秒(少于3600)最多需要2个字节即可表示,而某一毫秒(少于3600000)最多需要4个字节才可以表示。为了节省空间,OpenTSDB没有使用统一的长度,而是对特定的类型采用特性的编码方法。Qualifer的数据模型主要分为如下三种情况:秒、毫秒、秒和毫秒混合。

秒类型

当OpenTSDB接收到一个新的DataPoint的时候,如果请求中的时间戳是秒,那么就会插入一个如下模型的数据。

判断请求中的时间戳为秒或毫秒的方法是基于时间戳数值的大小,如果时间戳的值的超过无符号整数的最大值(即4个字节的长度),那么该时间戳是毫秒,否则为秒。

OpenTSDB在HBase中的底层数据结构设计

  • Value长度:Value的实际长度是Qualifier的最后3个bit的值加1,即(qualifier & 0x07) + 1。表示该时间戳对应的值的字节数。所以,值的字节数的范围是1到8个字节。
  • Value类型:Value的类型由Qualifier的倒数第4个bit表示,即(qualifier & 0x08)。如果值为1,表示Value的类型为float;如果值为0,表示Value的类型为long。
  • 时间戳:时间戳的值由Qualifier的第1到第12个bit表示,即(qualifier & 0xFFF0) >>>4。由于秒级的时间戳最大值不会大于3600,所以qualifer的第1个bit肯定不会是1。

毫秒类型

当OpenTSDB接收到一个新的DataPoint的时候,如果请求中的时间戳是毫秒,那么就会插入一个如下模型的数据。

OpenTSDB在HBase中的底层数据结构设计

  • Value长度:与秒类型相同。
  • Value类型:与秒类型相同。
  • 时间戳: 时间戳的值由Qualifier的第5到第26个bit表示,即(qualifier & 0x0FFFFFC0) >>>6。
  • 标志位:标志位由Qualifier的前4个bit表示。当该Qualifier表示毫秒级数据时,必须全为1,即(qualifier[0] & 0xF0) == 0xF0。
  • 第27到28个bit未使用。

混合类型

当同一小时的数据发生合并后,就会形成混合类型的Qualifier。

合并的方法很简单,就是按照时间戳顺序进行排序后,从小到大依次拼接秒类型和毫秒类型的Qualifier即可。

OpenTSDB在HBase中的底层数据结构设计

  • 秒类型和毫秒类型的数量没有限制,并且可以任意组合。
  • 不存在相同时间戳的数据,包括秒和毫秒的表示方式。

遍历混合类型中的所有DataPoint的方法是:

  • 从左到右,先判断前4个bit是否为0xF
  • 如果是,则当前DataPoint是毫秒型的,读取4个字节形成一个毫秒型的DataPoint
  • 如果否,则当前DataPoint是秒型的,读取2个字节形成一个秒型的DataPoint
  • 以此迭代即可遍历所有的DataPoint

③ Value设计

HBase Value部分用于保存一个或多个DataPoint的 具体某个时间戳对应的值

由于在Qualifier中已经保存了DataPoint Value的类型和DataPoint Value的长度,所以无论是秒级还是毫秒级的值,都可以用相同的表示方法,而混合类型就是多个DataPoint Value的拼接。

HBase Value按照长度可以分为如下几种类型:

单字节:当DataPoint Value为long型,且大于等于-128(Byte.MIN_VALUE),且少于或等于127(Byte.MAX_VALUE)的时候,使用1个字节存储。

两字节:当DataPoint Value为long型,且大于等于-32768(Short.MIN_VALUE),且少于或等于32767(Short.MAX_VALUE)的时候,使用2个字节存储。

四字节:当DataPoint Value为long型,且大于等于0x80000000(Integer.MIN_VALUE),且少于或等于0x7FFFFFFF(Integer.MAX_VALUE)的时候,使用4个字节存储。

八字节:当DataPoint Value为long型,且不是上面三种类型的时候,使用8个字节存储。当DataPoint Value为float型的时候,使用8个字节表示。

多字节:
按照时间戳的顺序,把多个Value拼接起来的数据模型如下:

OpenTSDB在HBase中的底层数据结构设计

每个格子表示一个DataPoint Value的值,这个DataPoint Value的长度可能是1或2或4或8个字节。

DataPoint Value的顺序与Qualifier中时间戳的顺序一一对应。
混合标志:如果最后1个字节为0x01,表示存在秒级类型和毫秒级类型混合的情况。

  • 指标名字:这个就是我们监控的指标,比如 sys.cpu.user;

  • 时间戳:监控数据产生的时间;

  • 值:Long 或者 Double 类型的数据,这个是监控指标在某个时间的具体值;

  • 标签:包括标签名字(tagk)和标签值(tagv),比如 tagk1=tagv1,主要用于描述数据属性,每条时序数据必须包含一组和多组的标签数据。目前 OpenTSDB 最多支持8组标签。

参考:

OpenTSDB原理系列-元数据模型

OpenTSDB原理系列-TSDB数据表设计

OpenTSDB的数据模型

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
Prometheus监控学习笔记之PromQL简单示例
0x00简单的时间序列选择返回度量指标http_requests_total的所有时间序列样本数据:http_requests_total返回度量指标名称为http_requests_total,标签分别是job"apiserver",handler"/api/comments"
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这