opentsdb的/api/uid接口源码跟踪

前言

前面的文章对tsdb的使用进行了记录,那么从现在开始会记录一些关于opentsdb内部实现的原理,标题写了是源码跟踪,其实跟踪源码就是为了探清楚它是怎么实现的。文章中不会贴上一堆一堆的源码,只会对它的实现过程,以及相关的内部结构进行讲述。本文的主要内容:

  1. uid和tsuid的概念以及这样设计的意义。
  2. /api/uid/assign,这个接口可以对metric,tagk,tagv申请uid,将会讲述其实现过程。
  3. /api/uid/uidmeta,这个接口类似于/api/uid/tsmeta,但是该接口是对uid的meta data进行操作,将会讲述其实现过程。
  4. /api/uid/tsmeta,这个接口可以对uid对应的meta data进行删除或者更新,将会讲述其实现过程。

uid以及tsuid

我们知道tsdb是通过metric,tags(包括tagk和tagv),timeserious来映射到value的。而且tsdb内部映射关系,是这样的:

  1. 将metric,tagk1,tagv1,tagk2,tagv2...按照规则生成对应的metric_UID,tagk1_UID,tagv1_UID,tagk2_UID,tagv2_UID,...
  2. 将第一步生成的全部uid生成一个tsUID,其形式如 [...]。
    一个tsUID就唯一映射到一个时间序列。

uid是由数字生成,并且官方默认的长度3byte,且说明了metric,tagk,tagv各自最大的uid个数是16,777,215。其计算方式是:

3(byte) = 24(bit)
Math.pow(2,24) = 16,777,216
uid默认是自增的形式,也就是从1一直自增到16,777,215。

这样设计的原因究竟是什么呢,为何平白无故耗费一些计算力去增加这个中间层面的uid呢。大兄弟不要急,下面会娓娓道来。

假如我们目前我们有这个时间序列

sys.cpu.0.user host=websv01.lga.mysite.com owner=operations. 

我们可以就用这个字符串直接映射到这个时间序列,可是其所占的空间为60byte。
现在我们将metric和tags转化成uid,假设其分别对应的uid如下:

000000000000000000000001 000000000000000000000001 000000000000000000000001 000000000000000000000001

进一步将二进制换算为16进制,得到的tsUID如下:

000001000001000001

可以看到对于这个时间时序,单个时间点存储空间就可以从60byte变成18byte,假如这个时间序列时间跨度很大,并且十分密集,那么能节约的存储空间就非常可观了,也是tsdb这样设计的原因所在。

/api/uid/assign

opentsdb是依赖于hbase的下面几个表进行工作的:

tsdb , tsdb-meta , tsdb-tree , tsdb-uid

这里先介绍tsdb-uid这个标,这个库是用来持久化metric,tags--->uid,以及uid--->metirc,tags映射关系的。
数据库的结构如下:

图1 tsdb-uid表结构

这更正一下:列族id和列族name下面的metric列其实应该是metrics。

rowkey为\x00是计数器,前面讲到uid是以自增的形式,这个计数器就是用于实现uid的自增,每申请一个新的metric(或者tagk,tagv)对应的计数器就会增加1,并将增加得到数值作为uid。

可以看到metric:sys.cpu.0.user的uid为000001,表中持久化了两者的映射关系:

sys.cpu.0.user ---> 000001
000001         ---> sys.cpu.0.user

好了,现在我们知道表结构了,下面我们来看一下为metric分配uid的过程,即/api/uid/assign接口的实现过程,对于tags的uid分配过程也是一样的。比较简单,看一遍下面不标准的流程图就会明白,嘿嘿。

图二 uid分配流程图
看了上图之后,可能会想到一个问题。

则多个线程对同一个字符串申请uid,此时就必须避免对uid的重复申请,tsdb中时这样处理的:

有一个线程安全的map,在申请之前会对name进行记录,申请完之后有会从中删除这条记录。为了避免重复申请,就必须在申请之前查询map中是否有这个name的相关记录,若有,则无法申请;若无,则可继续申请。

/api/uid/uidmeta

该接口可以对uid的meta data进行增删改,而meta data可以看成对uid的解释和说明。

上面说我们讲到了 tsdb-uid 这张表,现在将其表结构补充一下:

图3 tsdb-uid表结构

我们可以看到图1和图3的区别之处在于:

在name列族下面多了:metric_meta,tagk_meta,tagv_meta三个列,这三个列正是以json的格式分别存储了metric,tagk,tagv的meta_data。

meta_data的增删改查也就是对这个三个列进行操作,其过程也十分简单。有个地方需要提一下的是,那就是当有多个线程尝试去修改同一个meta_data时,一定需要避免发生脏读的情况。解决的办法是:

和采取版本号的机制类似,即Atomic Compare-And-Set (CAS) ,先从库中查询原始值,更新的时候会比较如果库中的值已经不是原始值了就会更新失败。

/api/uid/tsmeta

该接口的作用和 /api/uid/uidmeta 接口类似,只是这个接口是对tsUID的mete data进行操作。

tsUID的meta data并不是存储在 tsdb-uid 表中的,而是在tsdb-meta表中,现在我们来看一下 tsdb-meta 的表结构:

图4 tsdb-meta表结构

rowkey即是tsuid,name列族下面有ts_sct,和ts_meta两个列:

  1. ts_sct:存储该时间序列收到最新数据的时间,所以每次在对这个时间序列写入新的data point时,ts_sct都会更新。
  2. ts_meta:以json的形式存储tsuid对应的meta data。

清楚了这个表结构的之后,对meta data的增删改查的操作就十分简单。

在查询中,除了返回meta data之外,还会将tsuid解析为metric,tags,并在 tsdb-uid 表中查询metric,tags的meta data一并返回。

总结

关于/api/uid的内容到此就全部结束了,内容上较为简单,主要是熟悉其中的表结构以及考虑数据的线程安全。
嘿嘿嘿,以后博主画流程图会画好看一点。。。(#^.^#)

你可能感兴趣的:(opentsdb的/api/uid接口源码跟踪)