【译】openTSDB的​​UIDs​​​和​​TSUIDs​

本文译自:​​http://opentsdb.net/docs/build/html/user_guide/uids.html​

在openTSDB中,如果你写入一个时间序列点,这个数据点总是与一个metric,以及最少一个tag名,tag值相对应。每个metric,tag名和tag值均被赋上一个唯一的标识符。

1. UIDs 和 TSUIDs

每个​​metric​​​,​​tag name​​​,​​tag value​​​在第一次被存储时,或者显式的使用API,或是使用CLI工具时,都会被分配一个特殊的​​UID​​​。​​metric​​​和​​tag name/value​​​对的结合组成了一个时间序列的​​UID​​​或是​​TSUID​​。

1.1 UID

​UID​​对象包括的类型有:


  • ​metric​​:一个metric,像是sys.cpu.0 或 trades.per.second
  • ​tagk​​:一个tag 名,像host 或 symbol。这个总是tag key/value对中的key
  • ​tagv​​:一个tag value,像是web01 或 goog。这个总是tag key/value中的value部分。

1.2 Assignment

对于UID对象以及类来说,UID是一个正整数,且唯一。在存储系统中,每个​​metric,tagk,tagv​​都有一个​​counter​​计数。当你创建了一个新的​​tsdb-uid​​表时,每种类型的counter的值都是0。所以如果你put了一个新的数据,它的metric是​​sys.cpu.0​​以及一个tag对是:​​host=web01​​,那么你将得到3个新的UID对象,每个UID均是1。

当时间序列点被写入一个TSD中,每个新的​​tagk,tagv​​对象会自动分配一个​​UID​​。​​metric​​对象同样会检索新的​​UIDs​​,但是这种情况仅当​​auto metric setting​​被设置成true时。否则,有新metrics的数据点会被拒绝。对于每个即将存储的数据,UIDs会在一个缓冲的map中检索。如果检索失败,那么TSD将会尝试分配一个新的UID。

1.3 Storage

默认情况下,存储的时候,​​UIDs​​​用3Bytes编码,对于每个​​UID​​​类型,可知最大唯一ID是​​16777215(2^24-1)​​​。这样做的原因是:减少存储空间,以及减少TSD的内存​​(memory footprint)​​​占用。对于大多数用户,1600万的​​unique metric​​​,1600万的​​tag names/values​​应该是足够了。但是如果你对于某特定类型的确需要更多的数目,你可以修改openTSDB的源代码,并且在4Bytes甚至更大的范围上重新编译。对于2.2版本,你可以通过配置文件覆写UID大小。

1.3.1 警告:

如果你的确需要调整​​UID​​​的编码字节数,你必须在一个新的​​tsdb​​​以及​​tsdb-uid​​表中操作,否则结果将不会是你期待的那样。如果你在现有的集群中有数据,你必须导出数据,删除所有表,从头开始创建表,并且重新导入数据。

1.3.2 Display

UIDs能用不同的方式展现。最普通的方式是通过​​Http api​​,当3bytes的UID数据以十六进制编码时。例如:UID 1 在二进制中是:​​00000000 00000000 00000001​​。作为一个无符号的字节数组,你可以将其想象成​​[0,0,1]​​。编码成一个十六进制的字符串,那么值将是​​000001​​,字符串中的每个字节都被0s填充。

注:这里解释一下为啥是​​000001​​:

二进制:   0000 0000  0000 0000  0000 0001
十六进制: 0 0 0 0 0 1

二进制转十六进制,四位做一位。

1.4 Display【这里讲的是UID的展示问题】

当UID为255时,将会产生一个十六进制的值:​​0000FF​​(或者是一个字节数组:​​[0,0,255]​​)。为了将一个十进制的UID转换成十六进制,可以使用任何你喜欢的进制转换工具,如果不足6位,可以在其前补0以补足6位。为了将一个十六进制的UID转换成一个十进制的数,可以简单地将其前面的0s去除,然后使用一个工具实现转换。

在一些CLI工具以及一些长的文件中,一个UID可能会被展示成一个有符号的字节数组(由于java的原因),诸如上述的例子中[0,0,1]或是[0,0,-28]。为了将这些有符号的数组转换成无符号的字节数组,然后转换为十六进制。例如,-28的二进制表示是:​​10011100​​,该二进制相应的十进制表示是156,十六进制则是9C。

1.5 Modification

UIDs可以重命名,甚至是删除。可以通过CLI实现重命名,通常情况下,这个操作很安全,但是这个操作会影响包括这些UID的时间序列。例如:如果我们有一个时间序列:​​sys.cpu.user host=web01​​以及另外一个​​apache.requests host=web01​​,如果我们想重命名​​web01​​为​​web01.mysite.org​​,那么这两个时间序列均会受到影响,并且显示出最新的host名,所有的查询指向旧名字必须得到更新。如果一个输入的数据点拥有一个先前的UID标志,那么一个新的UID将会被分配(因为旧的UID标志已经被删除了,所以再次插入时,就会重新分配了)。

从版本2.2开始,删除UIDs可能比较棘手。


  • 删除一个metric是安全的,因为users可能不再查询任何数据,并且这些UIDs可能不再建议的API中得到调用。
  • 然而删除一个​​tag name/value​​​却能够造成查询失败。例如,如果你有时间序列:​​metric=sys.cpu.user​​​ 以及​​host=web01,web02,web03​​等,并且你删除了web02这个UID,那么查询将会扫描数据,包括序列:sys.cpu.user host=web02将会抛出一个异常,因为数据仍然存储在硬盘中。我们强烈建议你运行一遍FSCK,伴随一个查询去修复这些问题。

1.6 Why UIDs

这个问题经常被问及,所以在这做一个解释是有意义的。查询或者是分配UID经常在TSD中占用宝贵的时间周期,所以人们想知道采用​​raw name()​​​去命名metric或是​​hash metric​​是否会更快。的确,从写的角度去看,这些技术会稍微快些一,但是却有很多明显的缺点。(接下来就分析这些缺点)

1.6.1 Raw names(原名)

因为opentsdb采用HBase作为存储层,所以你能使用strings作为行键,按照当前的模式,你会有一个row key,它看起来是这个样子:​​sys.cpu.0.user 1292148000 host=websv01.lga.mysite.com owner=operations​​。排序对于存在模式来说是相似的,但是现在你使用70 bytes去存储而非是19 bytes。另外,row key必须在每次查询中来回写,这也会增加你的网络负载。所以使用UIDs存储将会节省空间。

下面解释一下为什么需要70 bytes去存储

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

字符共占3+1+3+1+1+1+4+10+4+1+7+1+3+1+6+1+3+5+1+10=67个字节。一个char字符占用1B。加上空格所占字节数,一共是67+3=70B.

1.6.2 Hashes【哈希算法】

另外一个想法就是简单地增加UIDs到4字节,然后计算字符串的hash值,像我们经常做的那样:正向映射,反向映射存储哈希值。当然,这会大量减少分配一个UID的时间,但是这里有一些问题。第一,当不同的名字返回相同的hash值时,你将碰到一些哈希冲突。你可能会尝试使用复杂的算法,甚至增加hash值到8字节,但即使你这么做,你还是可能会碰到冲突。第二,现在,对于每一个上传的数据,你将添加一个hash计算。因为必须计算hash,然后检验当前形成的hash值在UID table中是否已经存在。此时,每个数据点只执行查找。第三,你不能很容易的预分区你的HBase region。如果你知道在你的系统中,你有大概800个metrics(tags与此无关),你能对你的HBase表进行预分区操作,从而均匀地分布这800个metrics,从而提高你的初始写性能。

1.7 TSUIDs

当一个数据点被写入到openTSDB中,row key的格式是:​​<metric_UID>,<timestamp>,<tagk1_UID>,<tagv1_UID>.....<tagkn_UID>,<tagvn_UID>。​​通过简单的从row key中放弃timestamp,我们将得到一个结合的UIDs数组,形成一个特殊的timeseries ID。将这些字节编码成一个十六进制字符串,将会得到一个有用的TSUID,从而能够被各种API调用传递。从上述的UID例子中,每个metric,tag name/value均有一个UID 1,那么如若将我们的TSUID编码成一个十六进制字符串就是​​000001000001000001​​。

然而这个TSUID格式可能会很长,而且极不雅观,特别是对早期的UIDs(这里早期的UID指的是:最开始形成的那些UID)来说,所有的0s均有一些有用的理由:


  • 如果你知道每个UID的宽度(如上所述,默认3字节),那么你将轻松的从字符串中解析每个metric,tag name/value的UID
  • 为每一个时间序列分配一个唯一的数字ID引起锁问题,或者是同步问题(在一个时间序列可能被丢失的情况下,如果UID不能增加的话。)

​TSUIDs​​近期我会详细解释。


==2018 11 14==