一、背景

同事发来一个图片,说是日志显示,一个服务推送数据到opentsdb得到了http 500返回码。
经过排查,确实如此,而且还不少。
2024-03-27T06:47:16.png

二、现象

排查现象,有波动,怀疑是跟并发量有关系。

但是,查询昨日正常与异常数据,前者2万左右,后者几百。不像是并发量问题。

三、猜测

(1)内存问题

之前CDH集群报内存不足,赶紧进行了内存扩容。
但是,问题依旧。
2024-03-27T06:47:40.png

四、定位

研发仔细分析日志,并通过本地调试opentsdb接口,发现是某一个id出现了问题。遇到了opentsdb的三字节问题。

  • 什么是三字节问题
    其实“三字节问题”是 OpenTSDB 中一个具体的实现细节。在 OpenTSDB 中,每一个唯一的时间序列都会有一个与之对应的 ID,这个 ID 是由一个特定的编码算法生成的,长度为 3 字节。因此,对于每一个唯一的度量/标签组合,OpenTSDB 只能生成最多 2^24(即约 1677 万)个不同的 ID。如果你的时间序列数量超过了这个上限,OpenTSDB 就无法再生成新的 ID,你将无法添加新的时间序列。 换句话说,OpenTSDB 的“三字节问题”指的是对每一个度量/标签的组合,OpenTSDB 只能处理最多约 1677 万个唯一的时间序列。这是一个极端的情况,一般不会出现。然而,对于一些大规模、长期的数据收集任务,可能会达到这个上限。 如果你遇到了这个问题,如我前面提到的,你可能需要清理一部分数据,或者调整你的数据存储策略和时间序列设计。在不改动 OpenTSDB 代码的情况下,这些可能是目前解决这个问题的可行方案。如果有更多疑问,可以继续提问。
  • 如果删除了三年前的数据,那么ID会有么?

删除数据并不会直接释放其对应的时间序列 ID,因为 ID 一旦生成会被保留,以保证时间序列的唯一性。即使对应的数据点被删除,但在 OpenTSDB 中,这个 ID 依然会被保留,因此删除数据并不能解决时间序列 ID 耗尽的问题。 在 OpenTSDB 的设计中,没有直接的办法来重新生成或复用已使用过的时间序列 ID。这是为了保证每一个时间序列的唯一性和一致性。因此,“三字节问题”实际上是 OpenTSDB 的一个设计限制。 如果你遇到了这个问题,可能需要考虑采用其他策略进行优化,比如重新考虑和设计你的标签和度量的组织方式;或者利用 OpenTSDB 提供的数据预处理功能,将某些数据进行整合,从而减少时间序列的数量。 这是一个复杂的问题,需要根据你具体的应用场景和需求来进行解决。如果有更多的疑问或需要更深入的讨论,请随时提问。

五、解决(2024年4月19日)

当时间来到4月中旬,客户着急使用,可是已经没有了uid分配。

(1)测试环境测试

  • 查看当前\x00的tagv的值

    hbase shell
    scan 'tsdb-uid', {LIMIT => 3, REVERSED => false}
    

2024-04-19T03:20:00.png

  • 删除\x00的tagv
    \x00的tagv消失

    deleteall 'tsdb-uid',"\x00",'id:tagv'
    scan 'tsdb-uid', {LIMIT => 3, REVERSED => false}

2024-04-19T03:20:43.png

  • 新建一个tagv用于打数据。出现了\x00的tagv

2024-04-19T03:21:36.png

但是写入数据错误 (should never happen!!!)。估计是新数据需要新增tagv uid的时候,当前tsdb-uid表中tagv uid已经分配了出去。所以本次分配失败,返回给客户端500。
下次客户端继续发送put请求,继续请求分配tagv,tsdb继续将tagv的uid增长+1。

2024-04-19T03:21:57.png

  • 写一个脚本,一直写数据

虽然仍然报“should never happen!!!”,但是tsdb-uid表中的tagv uid的值在一直增长。使用如下程序继续。这个程序会打到一个之前没有的uid,

#!/bin/bash

while true
do
    current_time=$(date +%s)
    current_time_ms=$((current_time * 1000))
    current_second=$(date +%S)

    data=$(cat <<EOF
[
    {
        "metric": "jacky",
        "value": "$current_second",
        "timestamp": "$current_time_ms",
        "tags": {
            "dvid": "109",
            "type": "1",
            "did": "123456789012345609"
        }
    }
]
EOF
)

    echo "Sending data to OpenTSDB..."
    response=$(curl -X POST http://192.168.124.194:4242/api/put --header "Content-Type: application/json" -d "$data" -w "%{http_code}" -s)
    status_code=$(echo "$response" | tail -n1)
    response_body=$(echo "$response" | sed '$d')

    if [[ "$status_code" -eq 200 || "$status_code" -eq 204 ]]; then
        echo "Data sent successfully. Server responded with status code: $status_code"
    else
        echo "Failed to send data. Server responded with status code: $status_code"
        echo "Response body: $response_body"
    fi

    sleep 1
done
  • 随着上面程序的运行,tagv这个地方的value一直在增加。等到删除tagv \x00之前的值。也许当该值增加到之前的\0C\D4M应该就可以了 。

2024-04-19T03:23:04.png

  • 到了D4M的下一个D4N,数值停止了。

2024-04-19T03:23:14.png

  • we are in !
    2024-04-19T03:23:25.png
  • 开始正常写入数据
    2024-04-19T03:23:38.png

(2)处理线上

  • 查看uid使用情况

    [root@cdhslave3 ~]# tsdb uid grep tagv . | wc -l
    16776889
    [root@cdhslave3 ~]# tsdb uid grep metrics . | wc -l
    334181
    [root@cdhslave3 ~]# tsdb uid grep tagk . | wc -l
    7
    
  • 分析tagv 数量16776889的组成
# 大于等于4小于14字节的一共是16487127
data.tagv.less_than_14_bytes.txt 15488132
data.tagv.less_than_7_bytes.txt 998995

# 应该是正常的数量
data.tagv.less_than_4_bytes.txt 1000
data.tagv.非数字.txt 5445
data.tagv.more_than_14_bytes.txt 283317
  • 分析出来tagv的值后,组成命令
tsdb uid delete tagv 2357129 ;echo deleted!;
tsdb uid delete tagv 23571293 ;echo deleted!;
tsdb uid delete tagv 23571305 ;echo deleted!;
tsdb uid delete tagv 2357131 ;echo deleted!;
tsdb uid delete tagv 23571311 ;echo deleted!;
tsdb uid delete tagv 2357132 ;echo deleted!;
tsdb uid delete tagv 23571329 ;echo deleted!;
tsdb uid delete tagv 23571337 ;echo deleted!;
tsdb uid delete tagv 235713378 ;echo deleted!;
tsdb uid delete tagv 23571344 ;echo deleted!;
  • 因为命令比较长,所以实用split分离成15个文件,每个服务器跑3个。

(3)加急处理

按照每天60万的速度,删除1600多万的数据需要一个月左右。但是,用户等不及。

那就再想想办法,删除最近的10万数据先。

  • 下载所有tsdb-uid表的数据。
cd /tmp
cat <<'EOF'>commands.txt
scan 'tsdb-uid', {REVERSED => false}
exit
EOF

hbase shell commands.txt > data.all.txt
  • 分离出来tagv
cat data.all.txt | grep tagv > data.all.tagv.txt
  • 刷新,找到大约30~40多万行,第一个有问题的数据的位置。
    2024-04-19T03:29:09.png
  • 找到这行数据
└─$ cat data.all.tagv.txt| grep -n "value=319338$"
219835: \x03Z\xBD column=name:tagv, timestamp=1689656981443, value=319338
  • 从上面这行开始,往后看10万行
sed -n '219835,319835p' data.all.tagv.txt | awk '{print $(NF)}' | awk -F\= '{print $2}' | sort -n > data.all.tagv.result.txt
  • 修改成删除命令
vi data.all.tagv.result.txt

sed -i 's:^:tsdb uid delete tagv :g'
sed -i 's:$:;echo deleted!'

2024-04-19T03:29:55.png
然后放到线上去执行。

  • 删除\x00 tagv的值

当研发把异常代码注释掉之后,开始删除\x00。随着新数据metric+tags到达,将申请uid,然后促使\x00的tagv value开始递增变化。

hbase shell
deleteall 'tsdb-uid',"\x00",'id:tagv'
scan 'tsdb-uid', {LIMIT => 3, REVERSED => false}
  • 第二天一大早查看删除情况
    2024-04-19T03:30:55.png
  • 查看uid情况
    2024-04-19T03:31:04.png
  • 模拟数据打入,居然成功了。
    可能是opentsdb内部机制,实现了uid回收。
    2024-04-19T03:31:24.png

(4)剩余的异常uid删除

现在1600多万的异常uid还在以每天60左右的速度删除。当删除之后,基本上opentsdb数据库中基于tagv的value异常值将得到极大缓解。

最后修改:2024 年 05 月 11 日
如果觉得我的文章对你有用,请随意赞赏