一、故障发生的现场还原

那天凌晨两点,我正抱着电脑赶项目文档,突然接到运维组的紧急消息:负责智慧路灯时序数据存储的TDengine节点崩了,业务端的上报接口返回连接失败,园区的路灯数据已经3小时没落库了。我赶紧切换到远程桌面,登录到TDengine所在的服务器,敲了几个命令快速摸清楚状况。

1.1 故障触发的初步表现

最先看到的是业务端的错误日志,全是“Connection refused”的提示,说明TDengine的守护进程taosd根本没启动。接着我去查系统内核的底层日志,找进程被杀的真实原因,用这个命令:

# 查看系统内核级别的详细日志,过滤出与进程被杀相关的记录
dmesg | grep -i oom

输出里明确写着“Killed process 1234 (taosd) due to Out of Memory”,原来TDengine的节点进程是被系统的内存回收机制直接干掉的,因为凌晨那段时间设备上报的并发量突然拉满,节点资源扛不住了。

1.2 节点服务的临时重启尝试

我先试着用命令手动启动taosd服务,结果启动后查集群状态,发现这个节点的状态是异常的。再看数据目录的日志,原来预写日志(wal)出现了损坏,直接用旧数据启动服务会有问题,所以只能走数据恢复的路线,不能硬重启。我用这个命令查看集群所有节点的状态:

# 查询TDengine集群所有节点的运行状态
taos -s "show nodes;"

输出里这个故障节点的“status”字段是“down”,“error message”里写着“wal corrupted”,确认是预写日志损坏,无法直接读取旧数据。

二、TDengine节点宕机的核心原因排查

2.1 排查过程中的关键细节

我先确认了故障发生的时间点,是凌晨2:00到5:00之间,这段时间是园区路灯设备的上报峰值——平时每个路灯每秒上报2条数据,总共2000个路灯,峰值时每秒会有4000条数据写入。我查了taosd的配置文件,发现这个节点的内存配置只有4G,而TDengine默认的内存占用阈值设置得比较高,并发高的时候很快就把内存占满,触发了系统的OOM Killer。

2.2 损坏数据的定位

既然预写日志损坏了,旧节点的增量数据无法正常读取,还好之前开启了binlog(TDengine的二进制日志,用来记录所有写入操作),我找到binlog的文件路径,过滤故障时间段的binlog,把增量数据导出来。用的命令是:

# 解析指定时间范围内的binlog,生成对应的SQL语句,用来补增量数据
taosdbinlog -f /var/lib/taos/binlog/taoslog.000001 -s "2024-05-20 02:00:00" -e "2024-05-20 05:00:00" > incremental_data.sql

这个命令会把凌晨2点到5点之间的所有写入操作转换成可执行的SQL语句,这样就能把故障期间丢失的增量数据补回来。

三、数据恢复的实操过程

3.1 全量备份数据的恢复

我们之前每天凌晨1点会做一次TDengine的全量备份,用的是taosdump工具。我先启动一个新的TDengine节点,把备份的全量数据导入进去,命令如下:

# 从备份文件导入全量数据到新节点,恢复到故障发生前的状态
taosdump -h 192.168.1.100 -D smart_lamp -P 0 -o /backup/taos_dump_20240520.sql

这里的-h是新节点的IP,-D是要恢复的数据库名,-o是备份文件的路径。导入完成后,我用taos的查询命令确认全量数据的时间范围正确,覆盖了故障前的所有数据。

3.2 增量数据的补全

全量数据恢复后,现在要补凌晨2点到5点的增量数据,就是之前用taosdbinlog生成的incremental_data.sql,直接导入到恢复后的节点里:

# 导入增量数据到新节点,补故障期间丢失的写入记录
taos -h 192.168.1.100 -f incremental_data.sql

导入完成后,我用查询命令对比恢复前后的数据量,确认这3小时的5万条数据都已经补回来了,数据的时间范围和条数完全匹配,数据一致性没问题。

3.3 恢复后的节点验证

我用taos的命令查询新节点的状态,现在这个节点的status已经变成“ready”,再查业务的上报接口,返回正常,数据可以正常写入,这一步就完成了核心的故障恢复工作。

四、写入积压的处理方法

故障恢复后,发现业务端的本地队列里积压了大概5万条待写入的数据——因为故障期间业务的上报接口返回错误,客户端把数据存在本地队列里了,这些数据必须尽快处理,不然会影响后续的设备上报。

4.1 积压数据的识别

我先查了TDengine节点的写入队列状态,确认积压的具体数量:

# 查看TDengine守护进程的详细状态,包括写入队列的积压情况
taos -s "show daemon status;"

输出里的“write queue”字段显示有5万条待处理的任务,队列延迟已经超过了10秒,再这样下去会导致业务超时。

4.2 批量写入的优化技巧

原来的业务是单条写入TDengine,每次发一条insert语句,这样网络开销很大,我把积压的5万条数据整理成批量的insert语句,每100条一组,这样可以减少网络请求的次数,提高写入效率。我写了一个简单的shell脚本来批量生成:

#!/bin/bash
# 批量生成TDengine的批量插入语句,每100条为一组,减少网络开销
BACKLOG_FILE="backlog_data.txt"  # 积压数据的文件,格式是ts, lamp_id, status, temp
OUTPUT_FILE="batch_insert.sql"    # 生成的批量插入SQL文件

# 初始化批量插入的开头语句
echo "insert into smart_lamp.lamp_status values" > $OUTPUT_FILE
count=0  # 计数器,记录当前组的条数

# 遍历积压的每条数据,生成插入语句
while IFS=, read ts lid sta temp; do
    # 跳过表头行(ts, lamp_id, status, temp)
    if [ "$ts" != "timestamp" ]; then
        # 拼接一条数据的插入语句
        line=" ($ts, '$lid', $sta, $temp)"
        echo $line >> $OUTPUT_FILE
        ((count++))  # 计数器加1

        # 每满100条,加一个分号,开始新的一组插入语句
        if [ $count -eq 100 ]; then
            echo ";" >> $OUTPUT_FILE
            echo "insert into smart_lamp.lamp_status values" >> $OUTPUT_FILE
            count=0  # 计数器重置
        fi
    fi
done < $BACKLOG_FILE

# 处理最后不足100条的剩余数据
if [ $count -gt 0 ]; then
    echo ";" >> $OUTPUT_FILE
fi

这个脚本生成的批量插入语句,最后大概有500组,写入效率比单条插入高很多。

4.3 临时扩容分流的小操作

为了加快处理速度,我临时在集群里加了一个节点,把部分批量写入的请求分流到新节点,减少原节点的压力:

# 向TDengine集群添加一个新节点,用于分流写入压力
taos -s "create node '192.168.1.101:6030';"

分流之后,批量写入的耗时从原来的20分钟降到了3分钟,很快就把积压的5万条数据处理完了,业务恢复正常。

五、故障复盘与总结

5.1 应用场景

这次故障的应用场景是园区智慧路灯的时序数据存储,这类场景的特点是:数据带时间戳(时序型),写入并发高,查询都是按时间范围查询,TDengine刚好针对这类场景做了优化,但是如果节点配置不够,很容易出现OOM宕机的问题,所以这类场景下要特别注意节点的资源配置。

5.2 技术优缺点

TDengine的优点:一是对时序数据的写入和查询速度快,比MySQL快5-10倍,适合高并发的时序场景;二是自带集群功能,支持数据同步,有副本的话可以快速恢复;三是配置简单,上手容易,对运维人员友好。缺点:一是大并发写入的时候,内存占用高,节点配置不足容易宕机;二是对于复杂的多表关联查询,性能不如分布式关系型数据库;三是生态相比MySQL和PostgreSQL还不够完善,工具相对较少。

5.3 注意事项

从这次故障里,我总结了几个关键注意事项:1、要根据并发量给节点配置足够的内存,比如每1000条/秒的并发,至少配置8G内存,避免OOM;2、一定要开启全量备份和binlog,binlog是增量数据恢复的关键,全量备份是恢复的基础;3、节点的配置要统一,避免某个节点的配置太低导致集群不稳定;4、写入的时候尽量用批量插入,每100-500条一组,减少网络开销;5、要设置内存使用率报警,超过80%就告警,避免节点突然宕机。

5.4 文章总结

这次故障从发现到处理,大概用了1小时,核心步骤是:先排查故障原因(OOM宕机,wal损坏),然后用全量备份恢复节点,再用binlog补增量数据,最后处理写入积压。对于用TDengine的小伙伴来说,遇到节点宕机不要慌,先查日志找原因,再用备份和增量数据恢复,平时做好备份和配置优化,就能避免大部分故障。