opentsdb 小米正用时序数据库,解决这个“硬核”问题( 二 )


文章图片

图3存储在HBase中的opentsdb的行键设计
除了通过uid节省内存空之外,行键的构成也很有意思,即metric+timestamp+tagk1+tagv1...采用这种顺序是为了访问模式和性能:
首先,timestamp 不适合作为前缀。timestamp作为前缀容易造成热点问题,而且对于多数较大时间跨度的查询都需要对这段时间内的绝大多数数据做扫描,效率比较低下。其次,tag 也不适合作为前缀。tag作为前缀避免了热点问题,但无法避免查询的低效。主要原因是用户关心的是以包含metric的一簇数据,如果使用metric作为前缀,不但避免了热点问题,还能大大缩小扫描数据的范围。再次,timestamp应该放在tag之前。前面两点决定了必须以metric为前缀,那么如果tag放在timestamp前面有什么坏处呢?这里必须提到OpenTSDB设计的初衷。对于时序数据而言,我们不可能在保证高性能的情况下,同时满足高精度、长时间跨度和多标签(即tag)的查询。不同的TSDB对此有不同的折中。OpenTSDB选择限制tag(限制tag key数量,虽然可以配置)以保证比较长的时间跨度。因此,tag的筛选度低于时间这一因素,也就是说timestamp应该放在tag前面。另外,这里的时间戳是每小时的时间戳。限定符是事件时间戳相对于小时的偏移量,分为三种类型:秒、毫秒以及秒和毫秒的混合。在某些情况下(如下面的压缩),限定符还支持将多个时间戳拼接在一起,形成一个限定符,该限定符对应于一个接一个的拼接值。图4以秒为例,示出了整个糖化血红蛋白数据表[6]的结构。

opentsdb 小米正用时序数据库,解决这个“硬核”问题


文章图片

图4存储在主机数据库中的开放源码数据库的数据表结构
压缩
OpenTSDB压缩是将HBase数据表每行存储的多个限定符按时间顺序拼接成一个限定符,将多个值拼接成一个值,以节省存储空并提高查询效率。该设计主要基于以下事实:
由于时序数据的特点,用户很少会只读取某一个时刻的数据,往往是读取一个时序序列,实际上OpenTSDB也没有API提供对某一特定时间点的读操作。存储引擎HBase对每个时间点的存储格式是row key + column family + qualifier + value(略去无关内容)。显然,对于每行的多列数据来说,row key、column family是冗余的压缩的实现是一个线程从HBase中读出每一行数据,然后合并数据,写入新的限定符和相应的值,最后从HBase中删除过期的数据。这个过程增加了HBase客户端和HBase服务器的负载,会造成HBase性能的抖动。该功能一般在生产环境中关闭,当时序数据库所在的物理资源消耗比较低时,可以定期进行压缩,达到数据压缩的目的。
最佳实践
OpenTSDB作为高性能的TSDB,有很多优点,但是使用不当也会造成意想不到的后果。以下是一些实用的参考资料:
优化HBase:在很多情况下,HBase是读写处理流程的核心,也是耗时最多的环节,优化OpenTSDB首先要优化HBase。这些措施包括调整BlockCache、优化GC和开启压缩等。优化HBase本身是一个复杂的话题,这里不展开。使用explicitTags:对于基数(Cardinality)比较高的tag来说,使用explicitTags时,OpenTSDB会为HBase Client添加一个额外的过滤器Fuzzy Filter,使筛选过程更高效;对于多个tag的metric,使用explicitTags能过滤掉多余的数据,保证结果正确。启用盐化:启用盐化之后,OpenTSDB会启动多个线程并发对请求进行处理,降低请求的延时。控制tag的状态空间:合理选择tag,使所有tag的组合数不要过大。如前所述,tag key状态空间过大会导致HBase RegionServer的scan操作加载过多的无关数据,导致响应缓慢。Tag key的最大个数是可以配置的,谨慎考虑改变这个配置项。小米时间序列数据库服务

推荐阅读