openTSDB 介绍
openTSDB是基于Hbase的分布式的,可伸缩的时间序列数据库,作为时序性数据库,能作为监控系统 ,目前openTSDB支持秒级别的数据查询存储,并且数据会持久化保存在HBase表中,在官方文档,给出以下架构
1.为什么会需要做compact
这就要先介绍以下,openTSDB的数据在HBase的存储了,这里有个HB阿瑟数据表 tsdb表,这里面保存了 openTSD的数据,一条完整的数据包含一下维度
1)Metric:监控项。如 集群监控指标
2)Tags:Tags由tagk和tagv组成,
3)Value:一个Value表示一个metric的实际数值,比如99
4)Timestamp:时间戳,因为数据是基于时间的,一条数据 对应一个时间戳。
由于openTSD底层依赖于HBase 进行数据的保存,首先是TSDB的rowkey设计,TSDB是采用 :加盐+metric + timestamp + tagk1 + tagv1 + tagk2 + tagv2 + … + tagkn + tagvn,作为HBase TSDB表的rowkey
基本格式如下
open TSDB 为了减少rowkey的存储,openTSDB 会对每一个tag metric 都进行编码,所以TSDB 会依赖UID表,这个表对应了 每个tag 和编码的相互对应关系
最终形成了 下面的格式
2.追踪源码-1
public Deferred<Object> call(final Boolean allowed) throws Exception {
if (!allowed) {
return Deferred.fromResult(null);
}
last_ts = (ms_timestamp ? timestamp : timestamp * 1000);
long base_time = baseTime();
long incoming_base_time;
if (ms_timestamp) {
// drop the ms timestamp to seconds to calculate the base timestamp
incoming_base_time = ((timestamp / 1000) - ((timestamp / 1000) % Const.MAX_TIMESPAN));
} else {
incoming_base_time = (timestamp - (timestamp % Const.MAX_TIMESPAN));
}
if (incoming_base_time - base_time >= Const.MAX_TIMESPAN) {
// Need to start a new row as we've exceeded Const.MAX_TIMESPAN.
base_time = updateBaseTime((ms_timestamp ? timestamp / 1000 : timestamp));
}
更新基础时间,添加row到队列中
private long updateBaseTime(final long timestamp) {
final long base_time = timestamp - (timestamp % Const.MAX_TIMESPAN);
row = Arrays.copyOf(row, row.length);
Bytes.setInt(row, (int) base_time, Const.SALT_WIDTH() + tsdb.metrics.width());
RowKey.prefixKeyWithSalt(row); // in case the timestamp will be involved in
// salting later
tsdb.scheduleForCompaction(row, (int) base_time);
return base_time;
}
上面是TSDB的逻辑存储,每一个rowkey,是一个整点的时间戳,后面对应多个kv ,然后 TSDB 会把一个小时整个数据都会读取出来,做压缩。
3.compaction 做了什么
主要步骤: 读取,压缩,写入,删除
1.主要是对多个k v 进行压缩合并,把上述产生的 数据通过压缩把多个kv 合并成为一个k v,这也就是open TSDB compaction 需要做的事情
2. 每个TSDB实例 都会维护一个 ConcurrentSkipListMap 作为数据结构的CompactionQueue ,这里面记录着,添加进HBase 表的所有rowkey,,TSDB 会定时启动线程,去对每一个rowkey 都进行compact 操作CompactionQueue ,这里面记录着,向HBase 添加的所有rowkey
4.compact 代码追踪-2
这里做compact 的时候 有几个关键点:
private final int flush_interval; // 单位是s 多长时间会执行compact 线程
private final int min_flush_threshold; // rows 最小的压缩行
private final int max_concurrent_flushes; // rows 同时最大压缩行
private final int flush_speed; //压缩的速率
final int maxflushes = Math.max(min_flush_threshold,
size * flush_interval * flush_speed / Const.MAX_TIMESPAN);
final long now = System.currentTimeMillis();
flush(now / 1000 - Const.MAX_TIMESPAN - 1, maxflushes);
if (LOG.isDebugEnabled()) {
final int newsize = size();
LOG.debug("flush() took " + (System.currentTimeMillis() - now)
+ "ms, new queue size=" + newsize
+ " (" + (newsize - size) + ')');
}
会调用 tsdb.get(row),获取每一行数据的kv 进行判断压缩
private Deferred<ArrayList<Object>> flush(final long cut_off, int maxflushes) {
assert maxflushes > 0: "maxflushes must be > 0, but I got " + maxflushes;
// We can't possibly flush more entries than size().
maxflushes = Math.min(maxflushes, size());
if (maxflushes == 0) { // Because size() might be 0.
return Deferred.fromResult(new ArrayList<Object>(0));
}
final ArrayList<Deferred<Object>> ds = new ArrayList<Deferred<Object>>(Math.min(maxflushes, max_concurrent_flushes));
int nflushes = 0;
int seed = (int) (System.nanoTime() % 3);
for (final byte[] row : this.keySet()) {
if (maxflushes == 0) {
break;
}
if (seed == row.hashCode() % 3) {
continue;
}
final long base_time = Bytes.getUnsignedInt(row,
Const.SALT_WIDTH() + metric_width);
if (base_time > cut_off) {
break;
} else if (nflushes == max_concurrent_flushes) {
break;
}
if (super.remove(row) == null) { // We didn't remove anything.
continue; // So someone else already took care of this entry.
}
nflushes++;
maxflushes--;
size.decrementAndGet();
ds.add(tsdb.get(row).addCallbacks(compactcb, handle_read_error));
}
}
获取到具体的rowkey 对应的kv 之后,这里会做判断,如果对应的列是多个 就会进行进行数据的读取和删除等操作。