开源OpenTSDB是否支持多值模型?

回复

Shaco 发起了问题 • 1 人关注 • 0 个回复 • 83 次浏览 • 2019-07-10 09:43 • 来自相关话题

openTSDB源码详解之rowKey生成

LittleLawson 发表了文章 • 4 个评论 • 652 次浏览 • 2018-11-25 15:23 • 来自相关话题

openTSDB的一个非常好的设计就是其rowKey。下面笔者主要通过源码的角度来详细介绍一下其rowKey的生成步骤。
1. 相关处理类
openTSDB往hbase中写入数据的处理过程,我之前就已经分析过,主要涉及的类有:- `addPointInternal(...)`




这里主要讲解的是,一个row key是如何生成的。

2. 具体步骤
 2.1 row_size的确定
       2.1.1 `SALT_WIDTH`
首先对于一个rowKey,肯定是有大小的,但是这个rowKey的大小该怎么确定呢?因为这个rowKey是需要写入到HBase中的,也就是需要通过网络的传输才能到达HBase端,所以不可能无限长,即需要精确到一个具体的长度。
openTSDB中的rowKey的大小用变量row_size表示。其具体的生成规则如下图所示:




其中的 `SALT_WIDTH()`函数返回的是SALT WIDTH,它是一个static修饰的int型变量,初始值为0,如下图所示:




    2.1.2 `metric_width`,`tag_name_width`,`tag_value_width`
`metric_width` 代表的就是`metric`的长度。在openTSDB系统中,默认的长度是3。该值来自`tsdb.metrics.width()`函数的返回值。




`width()` 方法如下:




而id_width的值设置过程如下:








调用构造函数`UniqueId(...)`的过程如下:




最终可以看到这些值默认为3




针对笔者传输的数据,可以推导出最后生成的row_size的大小就是`row_size = 0 +  3 +  4 + 3*1 + 3*1 = 13`,经过debug验证,也可以看到的确就是13。
 2.2 字节数组`row`




 2.3 `pos`变量




问题1:使用`pos`变量的原因是,控制对字节数组row的复制控制【每将一个值复制到row之后,就需要动态改变row的起始位置,这个位置就是pos定义的】
因为我没有开启 加盐处理功能,所以这里的pos的初始值就是0。
 2.4 为字节数组`row`赋值




上述copyInRowKey(...)的功能就是将metric对应的`uid`值放入到`row`中。而其中的 `getOrCreateId()`方法则是获取metric对应的`uid`值。该方法主要调用的其它方法如下:
- `getIdAsync()` 方法




- 从缓存中获取uid




- Deferred 类的构造方法




- `copyInRowKey()`方法




注意上面一张图中row和下面一张图的row的变化。这个变化就体现了将metric的id放到了rowKey中。




 2.5 `pos`值的变化




因为已经处理了metric,所以需要将其pos自增一下。
同时,因为暂时不着急处理timestamp,所以再次将pos自增处理。
 2.6 `tag pair`的处理




最后,需要在row中放入 tag pair,方式是采用for循环处理。
主要调用方法如下:
- `resolveOrCreateAll()`




- `resolveAllInternal()`方法




 2.7 `row`的大致部分
经过上述几个步骤的处理,则会生成一个row值:




 2.8 `rowKey`的完善
上述的步骤只是生成了一个rowKey的框架,但是还没有完全生成一个rowKey,因为还有最后一个步骤。接着会进入`WriteCB`这个回调类,如下:




注意这里的offset的取值:`metrics.width() + Const.SALT_WIDTH()`这个是用于将当前value所在小时的时间戳放入到row中。所以需要偏移`metrics.width() + Const.SALT_WIDTH()` 这样的长度。这里使用base_time = 1542096000举例,在未执行Bytes.setInt()方法之前,row的值如下图示:




使用`Bytes`类的`setInt()`方法修改row的值。`setInt()`主要的是`>>>`运算,这个运算符的意思是指:右移指定的位数,左边的空白位置0。得到的row值如下:




可能有人会问,为什么使用的是`Bytes.setInt()`这个方法?要知道,openTSDB只是根据timestamp(实质是 `base_time`,这个`base_time`我在后面会补充介绍)去生成rowKey的一部分,所以这一部分是什么样的逻辑不重要了,重要的是这个逻辑需要保持相同的`base_time` 映射到固定的`byte` 值。而openTSDB在`rowKey`的生成过程中采取的方法就是使用`>>>`去处理(timestamp)`base_time` ,从而保证`rowKey`在`base_time`相同时,其`base_time`对应的`rowKey`部分也相同。
到这里,一个完整的rowKey就生成了。

3.其它
3.1 `base_time`字段的生成
这个`base_time`代表的值就是当前value传递过来所在的timestamp。例如:对于一个timestamp=1542088140,处理过程就是取该timestamp所在小时 0分0秒的timestamp值。这也能解释为什么opentsdb中的存储按照每小时进行行存储,而不是每个时间戳一行。




这个`Const.MAX_TIMESPAN`定义如下: /** Max time delta (in seconds) we can store in a column qualifier. */
public static final short MAX_TIMESPAN = 3600;
代表的是一个小时的秒跨度。对其`MAX_TIMESPAN` 取余之后,该余数就是秒数,再减去这个秒数就得到hh:00:00的值。上面的值处理之后,得到的base_time值变为了1542085200。如下示:




  查看全部
openTSDB的一个非常好的设计就是其rowKey。下面笔者主要通过源码的角度来详细介绍一下其rowKey的生成步骤。
1. 相关处理类
openTSDB往hbase中写入数据的处理过程,我之前就已经分析过,主要涉及的类有:- `addPointInternal(...)`
1.png

这里主要讲解的是,一个row key是如何生成的。

2. 具体步骤
  •  2.1 row_size的确定

       2.1.1 `SALT_WIDTH`
首先对于一个rowKey,肯定是有大小的,但是这个rowKey的大小该怎么确定呢?因为这个rowKey是需要写入到HBase中的,也就是需要通过网络的传输才能到达HBase端,所以不可能无限长,即需要精确到一个具体的长度。
openTSDB中的rowKey的大小用变量row_size表示。其具体的生成规则如下图所示:
2.png

其中的 `SALT_WIDTH()`函数返回的是SALT WIDTH,它是一个static修饰的int型变量,初始值为0,如下图所示:
3.png

    2.1.2 `metric_width`,`tag_name_width`,`tag_value_width`
`metric_width` 代表的就是`metric`的长度。在openTSDB系统中,默认的长度是3。该值来自`tsdb.metrics.width()`函数的返回值。
4.png

`width()` 方法如下:
5.png

而id_width的值设置过程如下:
6.png

7.png

调用构造函数`UniqueId(...)`的过程如下:
8.png

最终可以看到这些值默认为3
9.png

针对笔者传输的数据,可以推导出最后生成的row_size的大小就是`row_size = 0 +  3 +  4 + 3*1 + 3*1 = 13`,经过debug验证,也可以看到的确就是13。
  •  2.2 字节数组`row`

10.png

  •  2.3 `pos`变量

11.png

问题1:使用`pos`变量的原因是,控制对字节数组row的复制控制【每将一个值复制到row之后,就需要动态改变row的起始位置,这个位置就是pos定义的】
因为我没有开启 加盐处理功能,所以这里的pos的初始值就是0。
  •  2.4 为字节数组`row`赋值

12.png

上述copyInRowKey(...)的功能就是将metric对应的`uid`值放入到`row`中。而其中的 `getOrCreateId()`方法则是获取metric对应的`uid`值。该方法主要调用的其它方法如下:
- `getIdAsync()` 方法
13.png

- 从缓存中获取uid
14.png

- Deferred 类的构造方法
15.png

- `copyInRowKey()`方法
16.png

注意上面一张图中row和下面一张图的row的变化。这个变化就体现了将metric的id放到了rowKey中。
17.png

  •  2.5 `pos`值的变化

18.png

因为已经处理了metric,所以需要将其pos自增一下。
同时,因为暂时不着急处理timestamp,所以再次将pos自增处理。
  •  2.6 `tag pair`的处理

19.png

最后,需要在row中放入 tag pair,方式是采用for循环处理。
主要调用方法如下:
- `resolveOrCreateAll()`
20.png

- `resolveAllInternal()`方法
21.png

  •  2.7 `row`的大致部分

经过上述几个步骤的处理,则会生成一个row值:
22.png

  •  2.8 `rowKey`的完善

上述的步骤只是生成了一个rowKey的框架,但是还没有完全生成一个rowKey,因为还有最后一个步骤。接着会进入`WriteCB`这个回调类,如下:
23.png

注意这里的offset的取值:`metrics.width() + Const.SALT_WIDTH()`这个是用于将当前value所在小时的时间戳放入到row中。所以需要偏移`metrics.width() + Const.SALT_WIDTH()` 这样的长度。这里使用base_time = 1542096000举例,在未执行Bytes.setInt()方法之前,row的值如下图示:
24.png

使用`Bytes`类的`setInt()`方法修改row的值。`setInt()`主要的是`>>>`运算,这个运算符的意思是指:右移指定的位数,左边的空白位置0。得到的row值如下:
25.png

可能有人会问,为什么使用的是`Bytes.setInt()`这个方法?要知道,openTSDB只是根据timestamp(实质是 `base_time`,这个`base_time`我在后面会补充介绍)去生成rowKey的一部分,所以这一部分是什么样的逻辑不重要了,重要的是这个逻辑需要保持相同的`base_time` 映射到固定的`byte` 值。而openTSDB在`rowKey`的生成过程中采取的方法就是使用`>>>`去处理(timestamp)`base_time` ,从而保证`rowKey`在`base_time`相同时,其`base_time`对应的`rowKey`部分也相同。
到这里,一个完整的rowKey就生成了。

3.其它
  • 3.1 `base_time`字段的生成

这个`base_time`代表的值就是当前value传递过来所在的timestamp。例如:对于一个timestamp=1542088140,处理过程就是取该timestamp所在小时 0分0秒的timestamp值。这也能解释为什么opentsdb中的存储按照每小时进行行存储,而不是每个时间戳一行。
26.png

这个`Const.MAX_TIMESPAN`定义如下:
  /** Max time delta (in seconds) we can store in a column qualifier.  */
public static final short MAX_TIMESPAN = 3600;
代表的是一个小时的秒跨度。对其`MAX_TIMESPAN` 取余之后,该余数就是秒数,再减去这个秒数就得到hh:00:00的值。上面的值处理之后,得到的base_time值变为了1542085200。如下示:
27_.png

 

OpenTSDB(HBase)时序数据存储模型

过往记忆 发表了文章 • 0 个评论 • 716 次浏览 • 2018-11-06 16:28 • 来自相关话题

OpenTSDB基于HBase存储时序数据,在HBase层面设计RowKey规则为:metric+timestamp+datasource(tags)。HBase是一个KV数据库,一个时序数据(point)如果以KV的形式表示,那么其中的V必然是point的具体数值,而K就自然而然是唯一确定point数值的datasource+metric+timestamp。这种规律不仅适用于HBase,还适用于其他KV数据库,比如Kudu。

既然HBase中K是由datasource、metric以及timestamp三者构成,现在我们可以简单认为rowkey就为这三者的组合,那问题来了:这三者的组合顺序是怎么样的呢?

首先来看哪个应该排在首位。因为HBase中一张表的数据组织方式是按照rowkey的字典序顺序排列的,为了将同一种指标的所有数据集中放在一起,HBase将将metric放在了rowkey的最前面。假如将timestamp放在最前面,同一时刻的数据必然会写入同一个数据分片,无法起到散列的效果;而如果将datasource(即tags)放在最前面的话,这里有个更大的问题,就是datasource本身由多个标签组成,如果用户指定其中部分标签查找,而且不是前缀标签的话,在HBase里面将会变成大范围的扫描过滤查询,查询效率非常之低。举个上面的例子,如果将datasource放在最前面,那rowkey就可以表示为publisher=ultrarimfast.com&advertiser:google.com&gender:Male&country:USA_impressions_20110101000000,此时用户想查找20110101000000这个时间点所有发布在USA的所有广告的浏览量,即只根据country=USA这样一个维度信息查找指定时间点的某个指标,而且这个维度不是前缀维度,就会扫描大量的记录进行过滤。

确定了metric放在最前面之后,再来看看接下来应该将datasource放在中间呢还是应该将timestamp放在中间?将metric放在前面已经可以解决请求均匀分布(散列)的要求,因此HBase将timestamp放在中间,将datasource放在最后。试想,如果将datasource放在中间,也会遇到上文中说到的后缀维度查找的问题。

因此,OpenTSDB中rowkey的设计为:metric+timestamp+datasource,好了,那HBase就可以只设置一个columnfamily和一个column。那问题来了,OpenTSDB的这种设计有什么问题?在了解设计问题之前需要简单看看HBase在文件中存储KV的方式,即一系列时序数据在文件、内存中的存储方式,如下图所示:





上图是HBase中一个存储KeyValue(KV)数据的数据块结构,一个数据块由多个KeyValue数据组成,在我们的事例中KeyValue就是一个时序数据点(point)。其中Value结构很简单,就是一个数值。而Key就比较复杂了,rowkey+columnfamily+column+timestamp+keytype组成,其中rowkey等于metric+timestamp+datasource。

问题一:存在很多无用的字段。一个KeyValue中只有rowkey是有用的,其他字段诸如columnfamily、column、timestamp以及keytype从理论上来讲都没有任何实际意义,但在HBase的存储体系里都必须存在,因而耗费了很大的存储成本。

问题二:数据源和采集指标冗余。KeyValue中rowkey等metric+timestamp+datasource,试想同一个数据源的同一个采集指标,随着时间的流逝不断吐出采集数据,这些数据理论上共用同一个数据源(datasource)和采集指标(metric),但在HBase的这套存储体系下,共用是无法体现的,因此存在大量的数据冗余,主要是数据源冗余以及采集指标冗余。

问题三:无法有效的压缩。HBase提供了块级别的压缩算法-snappy、gzip等,这些通用压缩算法并没有针对时序数据进行设置,压缩效率比较低。HBase同样提供了一些编码算法,比如FastDiff等等,可以起到一定的压缩效果,但是效果并不佳。效果不佳的主要原因是HBase没有数据类型的概念,没有schema的概念,不能针对特定数据类型进行特定编码,只能选择通用的编码,效果可想而知。

问题四:不能完全保证多维查询能力。HBase本身没有schema,目前没有实现倒排索引机制,所有查询必须指定metric、timestamp以及完整的tags或者前缀tags进行查询,对于后缀维度查询也勉为其难。

虽说有这样那样的问题,但是OpenTSDB还是针对存储模型做了两个方面的优化:

优化一:timestamp并不是想象中细粒度到秒级或毫秒级,而是精确到小时级别,然后将小时中每一秒设置到列上。这样一行就会有3600列,每一列表示一小时的一秒。这样设置据说可以有效的取出一小时整的数据。

优化二:所有metrics以及所有标签信息(tags)都使用了全局编码将标签值编码成更短的bit,减少rowkey的存储数据量。上文分析HBase这种存储方式的弊端是说道会存在大量的数据源(tags)冗余以及指标(metric)冗余,有冗余是吧,那我就搞个编码,将string编码成bit,尽最大努力减少冗余。虽说这样的全局编码可以有效降低数据的存储量,但是因为全局编码字典需要存储在内存中,因此在很多时候(海量标签值),字典所需内存都会非常之大。

上述两个优化可以参考OpenTSDB这张经典的示意图:





  查看全部
OpenTSDB基于HBase存储时序数据,在HBase层面设计RowKey规则为:metric+timestamp+datasource(tags)。HBase是一个KV数据库,一个时序数据(point)如果以KV的形式表示,那么其中的V必然是point的具体数值,而K就自然而然是唯一确定point数值的datasource+metric+timestamp。这种规律不仅适用于HBase,还适用于其他KV数据库,比如Kudu。

既然HBase中K是由datasource、metric以及timestamp三者构成,现在我们可以简单认为rowkey就为这三者的组合,那问题来了:这三者的组合顺序是怎么样的呢?

首先来看哪个应该排在首位。因为HBase中一张表的数据组织方式是按照rowkey的字典序顺序排列的,为了将同一种指标的所有数据集中放在一起,HBase将将metric放在了rowkey的最前面。假如将timestamp放在最前面,同一时刻的数据必然会写入同一个数据分片,无法起到散列的效果;而如果将datasource(即tags)放在最前面的话,这里有个更大的问题,就是datasource本身由多个标签组成,如果用户指定其中部分标签查找,而且不是前缀标签的话,在HBase里面将会变成大范围的扫描过滤查询,查询效率非常之低。举个上面的例子,如果将datasource放在最前面,那rowkey就可以表示为publisher=ultrarimfast.com&advertiser:google.com&gender:Male&country:USA_impressions_20110101000000,此时用户想查找20110101000000这个时间点所有发布在USA的所有广告的浏览量,即只根据country=USA这样一个维度信息查找指定时间点的某个指标,而且这个维度不是前缀维度,就会扫描大量的记录进行过滤。

确定了metric放在最前面之后,再来看看接下来应该将datasource放在中间呢还是应该将timestamp放在中间?将metric放在前面已经可以解决请求均匀分布(散列)的要求,因此HBase将timestamp放在中间,将datasource放在最后。试想,如果将datasource放在中间,也会遇到上文中说到的后缀维度查找的问题。

因此,OpenTSDB中rowkey的设计为:metric+timestamp+datasource,好了,那HBase就可以只设置一个columnfamily和一个column。那问题来了,OpenTSDB的这种设计有什么问题?在了解设计问题之前需要简单看看HBase在文件中存储KV的方式,即一系列时序数据在文件、内存中的存储方式,如下图所示:

v2-f5949de041cae2639dd7db6d1ca8dc29_r.jpg

上图是HBase中一个存储KeyValue(KV)数据的数据块结构,一个数据块由多个KeyValue数据组成,在我们的事例中KeyValue就是一个时序数据点(point)。其中Value结构很简单,就是一个数值。而Key就比较复杂了,rowkey+columnfamily+column+timestamp+keytype组成,其中rowkey等于metric+timestamp+datasource。

问题一:存在很多无用的字段。一个KeyValue中只有rowkey是有用的,其他字段诸如columnfamily、column、timestamp以及keytype从理论上来讲都没有任何实际意义,但在HBase的存储体系里都必须存在,因而耗费了很大的存储成本。

问题二:数据源和采集指标冗余。KeyValue中rowkey等metric+timestamp+datasource,试想同一个数据源的同一个采集指标,随着时间的流逝不断吐出采集数据,这些数据理论上共用同一个数据源(datasource)和采集指标(metric),但在HBase的这套存储体系下,共用是无法体现的,因此存在大量的数据冗余,主要是数据源冗余以及采集指标冗余。

问题三:无法有效的压缩。HBase提供了块级别的压缩算法-snappy、gzip等,这些通用压缩算法并没有针对时序数据进行设置,压缩效率比较低。HBase同样提供了一些编码算法,比如FastDiff等等,可以起到一定的压缩效果,但是效果并不佳。效果不佳的主要原因是HBase没有数据类型的概念,没有schema的概念,不能针对特定数据类型进行特定编码,只能选择通用的编码,效果可想而知。

问题四:不能完全保证多维查询能力。HBase本身没有schema,目前没有实现倒排索引机制,所有查询必须指定metric、timestamp以及完整的tags或者前缀tags进行查询,对于后缀维度查询也勉为其难。

虽说有这样那样的问题,但是OpenTSDB还是针对存储模型做了两个方面的优化:

优化一:timestamp并不是想象中细粒度到秒级或毫秒级,而是精确到小时级别,然后将小时中每一秒设置到列上。这样一行就会有3600列,每一列表示一小时的一秒。这样设置据说可以有效的取出一小时整的数据。

优化二:所有metrics以及所有标签信息(tags)都使用了全局编码将标签值编码成更短的bit,减少rowkey的存储数据量。上文分析HBase这种存储方式的弊端是说道会存在大量的数据源(tags)冗余以及指标(metric)冗余,有冗余是吧,那我就搞个编码,将string编码成bit,尽最大努力减少冗余。虽说这样的全局编码可以有效降低数据的存储量,但是因为全局编码字典需要存储在内存中,因此在很多时候(海量标签值),字典所需内存都会非常之大。

上述两个优化可以参考OpenTSDB这张经典的示意图:

v2-1e76d8c875d96b6f7b1331997542c44f_hd.jpg

 

OpenTSDB 入门教程

过往记忆 发表了文章 • 0 个评论 • 1215 次浏览 • 2018-10-18 13:35 • 来自相关话题

架构简介

这里我们简单看一下它的架构,如下图所示:





其最主要的部件就是TSD了,这是接收数据并存储到HBase处理的核心所在。而带有C(collector)标志的Server,则是数据采集源,将数据发给 TSD服务。

安装 OpenTSDB

为了安装 OpenTSDB ,都需要以下条件和软件:
Linux操作系统JRE 1.6 or laterHBase 0.92 or later安装GnuPlot

如果你还想使用自带的界面,则需要安装GnuPlot 4.2及以后版本,以及gd和gd-devel等。这里我们选择了GnuPlot 5.0.1的版本。

根据情况执行(没有就装),安装所需软件
$ sudo yum install -y gd gd-devel libpng libpng-devel
之后安装GnuPlot:$ tar zxvf gnuplot-5.0.1.tar.gz
$ cd gnuplot-5.0.1
$ ./configure
$ make
$ sudo make install安装HBase

首先,确保设置了JAVA_HOME:
$ echo $JAVA_HOME
/usr这个不多说了,非常简单,只需要按照 https://hbase.apache.org/book.html#quickstart 这里所说,下载、解压、修改配置文件、启动即可。

这时候,再设置HBASE_HOME:$ echo $HBASE_HOME
/opt/hbase-1.0.1.1之后便可启动hbase:$ /opt/hbase-1.0.1.1/bin/start-hbase.sh
starting master, logging to /opt/hbase-1.0.1.1/logs/hbase-vagrant-master-localhost.localdomain.out
安装 OpenTSDB

这个也很简单,如果build失败,那肯定是缺少Make或者Autotools等东西,用包管理器安装即可。
$ git clone git://github.com/OpenTSDB/opentsdb.git
$ cd opentsdb
$ ./build.sh创建表OpenTSDB所需要的表结构:
$ env COMPRESSION=NONE ./src/create_table.sh
2016-01-08 06:17:58,045 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
HBase Shell; enter ‘help‘ for list of supported commands.
Type “exit” to leave the HBase Shell
Version 1.0.1.1, re1dbf4df30d214fca14908df71d038081577ea46, Sun May 17 12:34:26 PDT 2015


create ‘tsdb-uid’,
{NAME => ‘id’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’},
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 1.3180 seconds


Hbase::Table – tsdb-uid


create ‘tsdb’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2400 seconds


Hbase::Table – tsdb


create ‘tsdb-tree’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2160 seconds


Hbase::Table – tsdb-tree


create ‘tsdb-meta’,
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.4480 seconds


Hbase::Table – tsdb-meta 在habse shell里,可以看到表已经创建成功。> list
TABLE
tsdb
tsdb-meta
tsdb-tree
tsdb-uid
4 row(s) in 0.0160 seconds 表创建之后,即可启动tsd服务,只需要运行如下命令:$ build/tsdb tsd
如果看到输出:2016-01-09 05:51:10,875 INFO [main] TSDMain: Ready to serve on /0.0.0.0:4242
即可认为启动成功。

保存数据到OpenTSDB

在安装并启动所有服务之后,我们就来尝试发送1条数据吧。

最简单的保存数据方式就是使用telnet。
$ telnet localhost 4242
put sys.cpu.user 1436333416 23 host=web01 user=10001
这时,从 OpenTSDB 自带界面都可以看到这些数据。 由于sys.cpu.sys的数据只有一条,所以 OpenTSDB 只能看到一个点。

下图为 OpenTSDB 自带的查询界面,访问http://localhost:4242即可。





OpenTSDB中的数据存储结构

我们来看看 OpenTSDB 的重要概念uid,先从HBase中存储的数据开始吧,我们来看一下它都有哪些表,以及这些表都是干什么的。

tsdb:存储数据点
hbase(main):003:0> scan 'tsdb'
ROW COLUMN+CELL
\x00\x00\x01U\x9C\xAEP\x00\x column=t:q\x80, timestamp=1436350142588, value=\x17
00\x01\x00\x00\x01\x00\x00\x
02\x00\x00\x02
1 row(s) in 0.2800 seconds 可以看出,该表只有一条数据,我们先不管rowid,只来看看列,只有一列,值为0x17,即十进制23,即该metric的值。

左面的row key则是 OpenTSDB 的特点之一,其规则为:metric + timestamp + tagk1 + tagv1… + tagkN + tagvN
以上属性值均为对应名称的uid。

我们上面添加的metric为:sys.cpu.user 1436333416 23 host=web01 user=10001
一共涉及到5个uid,即名为sys.cpu.user的metric,以及host和user两个tagk及其值web01和10001。

上面数据的row key为:\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02
具体这个row key是怎么算出来的,我们来看看tsdb-uid表。

tsdb-uid:存储name和uid的映射关系

下面tsdb-uid表的数据,各行之间人为加了空行,为方便显示。




tsdb-uid用来保存名字和UID(metric,tagk,tagv)之间互相映射的关系,都是成组出现的,即给定一个name和uid,会保存(name,uid)和(uid,name)两条记录。

我们一共看到了8行数据。

前面我们在tsdb表中已经看到,metric数据的row key为\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02,我们将其分解下,用+号连起来(从name到uid的映射为最后5行):\x00\x00\x01 + U + \x9C\xAE + P + \x00\x00\x01 + \x00\x00\x01 + \x00\x00\x02 + \x00\x00\x02
sys.cpu.user 1436333416 host = web01 user = 10001 可以看出,这和我们前面说到的row key的构成方式是吻合的。

需要着重说明的是时间戳的存储方式

虽然我们指定的时间是以秒为单位的,但是,row key中用到的却是以一小时为单位的,即:1436333416 – 1436333416 % 3600 = 1436331600。

1436331600转换为16进制,即0x55 0x9c 0xae 0x50,而0x55即大写字母U,0x50为大写字母P,这就是4个字节的时间戳存储方式。相信下面这张图能帮助各位更好理解这个意思,即一小时只有一个row key,每秒钟的数据都会存为一列,大大提高查询的速度。




反过来,从uid到name也一样,比如找uid为\x00\x00\x02的tagk,我们从上面结果可以看到,该row key(\x00\x00\x02)有4列,而column=name:tagk的value就是user,非常简单直观。

重要:我们看到,上面的metric也好,tagk或者tagv也好,uid只有3个字节,这是 OpenTSDB 的默认配置,三个字节,应该能表示1600多万的不同数据,这对metric名或者tagk来说足够长了,对tagv来说就不一定了,比如tagv是ip地址的话,或者电话号码,那么这个字段就不够长了,这时可以通过修改源代码来重新编译 OpenTSDB 就可以了,同时要注意的是,重编以后,老数据就不能直接使用了,需要导出后重新导入。

tsdb-meta:元数据表

我们再看下第三个表tsdb-meta,这是用来存储时间序列索引和元数据的表。这也是一个可选特性,默认是不开启的,可以通过配置文件来启用该特性,这里不做特殊介绍了。





tsdb-tree:树形表

第4个表是tsdb-tree,用来以树状层次关系来表示metric的结构,只有在配置文件开启该特性后,才会使用此表,这里我们不介绍了,可以自己尝试。

通过HTTP接口保存数据

保存数据除了我们前面用到的telnet方式,也可以选择HTTP API或者批量导入工具“` import( http://opentsdb.net/docs/build ... .html )

假设我们有如下数据,保存为文件mysql.json:[ { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716527, "value": 1234, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716529, "value": 2345, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716627, "value": 3456, "tags": { "host": "web02", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716727, "value": 6789, "tags": { "host": "web01", "dc": "tianjin" } } ]
之后执行如下命令:$ curl -X POST -H “Content-Type: application/json” http://localhost:4242/api/put -d @mysql.json

即可将数据保存到 OpenTSDB 了。

查询数据

看完了如何保存数据,我们再来看看如何查询数据。

查询数据可以使用query接口,它既可以使用get的query string方式,也可以使用post方式以JSON格式指定查询条件,这里我们以后者为例,对刚才保存的数据进行说明。

首先,保存如下内容为search.json:
{ "start": 1435716527, "queries": [ { "metric": "mysql.innodb.rowlocktime", "aggregator": "avg", "tags": { "host": "*", "dc": "beijing" } } ] }
执行如下命令进行查询:$ curl -s -X POST -H "Content-Type: application/json" http://localhost:4242/api/query -d @search.json | jq . [ { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web01", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716527": 1234, "1435716529": 2345 } }, { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web02", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716627": 3456 } } ]
 
可以看出,我们保存了dc=tianjin的数据,但是并没有在此查询中返回,这是因为,我们指定了dc=beijing这一条件。

值得注意的是,tags参数在新版本2.2中,将不被推荐,取而代之的是filters参数。

总结

可以看出来, OpenTSDB 还是非常容易上手的,尤其是单机版,安装也很简单。有HBase作为后盾,查询起来也非常快,很多大公司,类似雅虎等,也都在用此软件。

但是,大规模用起来,多个TDB以及多存储节点等,应该都需要专业、细心的运维工作了。 查看全部
架构简介

这里我们简单看一下它的架构,如下图所示:

7bzsydidhh.jpeg

其最主要的部件就是TSD了,这是接收数据并存储到HBase处理的核心所在。而带有C(collector)标志的Server,则是数据采集源,将数据发给 TSD服务。

安装 OpenTSDB

为了安装 OpenTSDB ,都需要以下条件和软件:
  • Linux操作系统
  • JRE 1.6 or later
  • HBase 0.92 or later
  • 安装GnuPlot


如果你还想使用自带的界面,则需要安装GnuPlot 4.2及以后版本,以及gd和gd-devel等。这里我们选择了GnuPlot 5.0.1的版本。

根据情况执行(没有就装),安装所需软件
$ sudo yum install -y gd gd-devel libpng libpng-devel
之后安装GnuPlot:
$ tar zxvf gnuplot-5.0.1.tar.gz
$ cd gnuplot-5.0.1
$ ./configure
$ make
$ sudo make install
安装HBase

首先,确保设置了JAVA_HOME:
$ echo $JAVA_HOME
/usr
这个不多说了,非常简单,只需要按照 https://hbase.apache.org/book.html#quickstart 这里所说,下载、解压、修改配置文件、启动即可。

这时候,再设置HBASE_HOME:
$ echo $HBASE_HOME
/opt/hbase-1.0.1.1
之后便可启动hbase:
$ /opt/hbase-1.0.1.1/bin/start-hbase.sh
starting master, logging to /opt/hbase-1.0.1.1/logs/hbase-vagrant-master-localhost.localdomain.out
安装 OpenTSDB

这个也很简单,如果build失败,那肯定是缺少Make或者Autotools等东西,用包管理器安装即可。
$ git clone git://github.com/OpenTSDB/opentsdb.git
$ cd opentsdb
$ ./build.sh
创建表OpenTSDB所需要的表结构:
$ env COMPRESSION=NONE ./src/create_table.sh
2016-01-08 06:17:58,045 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
HBase Shell; enter ‘help‘ for list of supported commands.
Type “exit” to leave the HBase Shell
Version 1.0.1.1, re1dbf4df30d214fca14908df71d038081577ea46, Sun May 17 12:34:26 PDT 2015


create ‘tsdb-uid’,
{NAME => ‘id’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’},
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 1.3180 seconds


Hbase::Table – tsdb-uid


create ‘tsdb’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2400 seconds


Hbase::Table – tsdb


create ‘tsdb-tree’,
{NAME => ‘t’, VERSIONS => 1, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.2160 seconds


Hbase::Table – tsdb-tree


create ‘tsdb-meta’,
{NAME => ‘name’, COMPRESSION => ‘NONE’, BLOOMFILTER => ‘ROW’}
0 row(s) in 0.4480 seconds


Hbase::Table – tsdb-meta
在habse shell里,可以看到表已经创建成功。
> list
TABLE
tsdb
tsdb-meta
tsdb-tree
tsdb-uid
4 row(s) in 0.0160 seconds
表创建之后,即可启动tsd服务,只需要运行如下命令:
$ build/tsdb tsd
如果看到输出:
2016-01-09 05:51:10,875 INFO [main] TSDMain: Ready to serve on /0.0.0.0:4242  
即可认为启动成功。

保存数据到OpenTSDB

在安装并启动所有服务之后,我们就来尝试发送1条数据吧。

最简单的保存数据方式就是使用telnet。
$ telnet localhost 4242
put sys.cpu.user 1436333416 23 host=web01 user=10001
这时,从 OpenTSDB 自带界面都可以看到这些数据。 由于sys.cpu.sys的数据只有一条,所以 OpenTSDB 只能看到一个点。

下图为 OpenTSDB 自带的查询界面,访问http://localhost:4242即可。

bvqbhf0v5g.jpeg

OpenTSDB中的数据存储结构

我们来看看 OpenTSDB 的重要概念uid,先从HBase中存储的数据开始吧,我们来看一下它都有哪些表,以及这些表都是干什么的。

tsdb:存储数据点
hbase(main):003:0> scan 'tsdb'  
ROW COLUMN+CELL
\x00\x00\x01U\x9C\xAEP\x00\x column=t:q\x80, timestamp=1436350142588, value=\x17
00\x01\x00\x00\x01\x00\x00\x
02\x00\x00\x02
1 row(s) in 0.2800 seconds
可以看出,该表只有一条数据,我们先不管rowid,只来看看列,只有一列,值为0x17,即十进制23,即该metric的值。

左面的row key则是 OpenTSDB 的特点之一,其规则为:
metric + timestamp + tagk1 + tagv1… + tagkN + tagvN  
以上属性值均为对应名称的uid。

我们上面添加的metric为:
sys.cpu.user 1436333416 23 host=web01 user=10001  
一共涉及到5个uid,即名为sys.cpu.user的metric,以及host和user两个tagk及其值web01和10001。

上面数据的row key为:
\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02
具体这个row key是怎么算出来的,我们来看看tsdb-uid表。

tsdb-uid:存储name和uid的映射关系

下面tsdb-uid表的数据,各行之间人为加了空行,为方便显示。
kxb9h2rpw2.jpeg

tsdb-uid用来保存名字和UID(metric,tagk,tagv)之间互相映射的关系,都是成组出现的,即给定一个name和uid,会保存(name,uid)和(uid,name)两条记录。

我们一共看到了8行数据。

前面我们在tsdb表中已经看到,metric数据的row key为\x00\x00\x01U\x9C\xAEP\x00\x00\x01\x00\x00\x01\x00\x00\x02\x00\x00\x02,我们将其分解下,用+号连起来(从name到uid的映射为最后5行):
\x00\x00\x01 + U + \x9C\xAE + P + \x00\x00\x01 + \x00\x00\x01 + \x00\x00\x02  + \x00\x00\x02
sys.cpu.user 1436333416 host = web01 user = 10001
可以看出,这和我们前面说到的row key的构成方式是吻合的。

需要着重说明的是时间戳的存储方式

虽然我们指定的时间是以秒为单位的,但是,row key中用到的却是以一小时为单位的,即:1436333416 – 1436333416 % 3600 = 1436331600。

1436331600转换为16进制,即0x55 0x9c 0xae 0x50,而0x55即大写字母U,0x50为大写字母P,这就是4个字节的时间戳存储方式。相信下面这张图能帮助各位更好理解这个意思,即一小时只有一个row key,每秒钟的数据都会存为一列,大大提高查询的速度。
8an6jpc5it.jpeg

反过来,从uid到name也一样,比如找uid为\x00\x00\x02的tagk,我们从上面结果可以看到,该row key(\x00\x00\x02)有4列,而column=name:tagk的value就是user,非常简单直观。

重要:我们看到,上面的metric也好,tagk或者tagv也好,uid只有3个字节,这是 OpenTSDB 的默认配置,三个字节,应该能表示1600多万的不同数据,这对metric名或者tagk来说足够长了,对tagv来说就不一定了,比如tagv是ip地址的话,或者电话号码,那么这个字段就不够长了,这时可以通过修改源代码来重新编译 OpenTSDB 就可以了,同时要注意的是,重编以后,老数据就不能直接使用了,需要导出后重新导入。

tsdb-meta:元数据表

我们再看下第三个表tsdb-meta,这是用来存储时间序列索引和元数据的表。这也是一个可选特性,默认是不开启的,可以通过配置文件来启用该特性,这里不做特殊介绍了。

rqjvnt039d.jpeg

tsdb-tree:树形表

第4个表是tsdb-tree,用来以树状层次关系来表示metric的结构,只有在配置文件开启该特性后,才会使用此表,这里我们不介绍了,可以自己尝试。

通过HTTP接口保存数据

保存数据除了我们前面用到的telnet方式,也可以选择HTTP API或者批量导入工具“` import( http://opentsdb.net/docs/build ... .html

假设我们有如下数据,保存为文件mysql.json:
[ { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716527, "value": 1234, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716529, "value": 2345, "tags": { "host": "web01", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716627, "value": 3456, "tags": { "host": "web02", "dc": "beijing" } }, { "metric": "mysql.innodb.rowlocktime", "timestamp": 1435716727, "value": 6789, "tags": { "host": "web01", "dc": "tianjin" } } ]
之后执行如下命令:
$ curl -X POST -H “Content-Type: application/json” http://localhost:4242/api/put -d @mysql.json

即可将数据保存到 OpenTSDB 了。

查询数据

看完了如何保存数据,我们再来看看如何查询数据。

查询数据可以使用query接口,它既可以使用get的query string方式,也可以使用post方式以JSON格式指定查询条件,这里我们以后者为例,对刚才保存的数据进行说明。

首先,保存如下内容为search.json:
{ "start": 1435716527, "queries": [ { "metric": "mysql.innodb.rowlocktime", "aggregator": "avg", "tags": { "host": "*", "dc": "beijing" } } ] }
执行如下命令进行查询:
$ curl -s -X POST -H "Content-Type: application/json" http://localhost:4242/api/query -d @search.json | jq . [ { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web01", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716527": 1234, "1435716529": 2345 } }, { "metric": "mysql.innodb.rowlocktime", "tags": { "host": "web02", "dc": "beijing" }, "aggregateTags": [], "dps": { "1435716627": 3456 } } ] 

 
可以看出,我们保存了dc=tianjin的数据,但是并没有在此查询中返回,这是因为,我们指定了dc=beijing这一条件。

值得注意的是,tags参数在新版本2.2中,将不被推荐,取而代之的是filters参数。

总结

可以看出来, OpenTSDB 还是非常容易上手的,尤其是单机版,安装也很简单。有HBase作为后盾,查询起来也非常快,很多大公司,类似雅虎等,也都在用此软件。

但是,大规模用起来,多个TDB以及多存储节点等,应该都需要专业、细心的运维工作了。

opentsdb数据存储问题

ProgramGeek 回复了问题 • 2 人关注 • 1 个回复 • 1015 次浏览 • 2018-09-28 11:39 • 来自相关话题

opentsdb一般在什么场景下使用?

LittleLawson 回复了问题 • 3 人关注 • 3 个回复 • 3254 次浏览 • 2018-08-11 21:27 • 来自相关话题

opentsdb启动出现 RegionClient: Unexpected exception from downstream on ,请问有人碰到过

回复

lvwenyuan 发起了问题 • 1 人关注 • 0 个回复 • 1382 次浏览 • 2018-07-11 10:40 • 来自相关话题


中国HBase技术社区微信公众号:
hbasegroup

欢迎加入HBase生态+Spark社区钉钉大群