一、故障发生的现场还原
那天凌晨两点,我正抱着电脑赶项目文档,突然接到运维组的紧急消息:负责智慧路灯时序数据存储的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的小伙伴来说,遇到节点宕机不要慌,先查日志找原因,再用备份和增量数据恢复,平时做好备份和配置优化,就能避免大部分故障。
Comments