TiDB 集群主机故障后的 PD 与 TiKV 节点恢复实战
集群信息:TiDB v6.5.0(社区版),混合部署,3 个 PD 节点(xxx.181 / 182 / 183),TiKV 节点分布于多台主机。故障时间:2026/07/11 17:25故障起因:宿主机故障导致 PD 与 TiKV 节点异常关闭,重启后无法正常拉起。涉及组件:PD、TiKV。
一、故障背景
某生产 TiDB 集群 tidb-prod 所在宿主机发生故障,导致该主机上的 PD 节点(xxx.181)与 TiKV 节点(/data1/tikv-20160)非正常关闭。故障恢复后尝试重启服务,PD 与 TiKV 均无法正常启动,日志中分别报出 快照文件丢失 和 SST 文件损坏 两类错误。
整个恢复过程分为两个阶段:
- PD 节点恢复:经历了快照损坏 → etcd 成员冲突 → 权限拒绝 → 元数据同步超时 共四个问题;
- TiKV 节点恢复:经历了 SST/WAL 不一致 → 删除故障 store → 重建数据目录 共三个步骤。
下面按时间线逐段记录。
二、PD 节点恢复
2.1 第一阶段:查看集群状态,定位故障节点
首先通过 tiup 查看集群整体状态与 leader 分布:
tiup cluster display tidb-prod
输出显示 xxx.181 上的 PD 节点处于 Down 状态,其余两个 PD(182、183)正常。随后检查故障 PD 的日志:
less /tidb-deploy/pd-2379/log/pd.log
2.2 第二阶段:快照文件损坏导致 PD Panic
PD 启动日志的关键片段如下:
[2026/07/11 03:25:12.871 +08:00] [INFO] [etcd.go:305] ["starting an etcd server"]
[name=pd-xxx.181-2379] [data-dir=/tidb-data/pd-2379]
[force-new-cluster=false]
[initial-cluster-state=new]
...
[2026/07/11 03:25:13.544 +08:00] [INFO] [server.go:462] ["recovered v2 store from snapshot"]
[snapshot-index=1671816728] [snapshot-size="24 kB"]
[2026/07/11 03:25:13.547 +08:00] [WARN] [db.go:92] ["failed to find [SNAPSHOT-INDEX].snap.db"]
[snapshot-index=1671816728]
[snapshot-file-path=/tidb-data/pd-2379/member/snap/0000000063a5e618.snap.db]
[error="snap: snapshot file doesn't exist"]
[2026/07/11 03:25:13.547 +08:00] [PANIC] [server.go:473] ["failed to recover v3 backend from snapshot"]
[error="failed to find database snapshot file (snap: snapshot file doesn't exist)"]
[2026/07/11 03:25:13.547 +08:00] [FATAL] [log.go:72] [panic]
[recover="\"invalid memory address or nil pointer dereference\""]
分析:
- 配置项
initial-cluster-state=new且force-new-cluster=false,说明 PD 走的是正常启动路径(而非强制重建集群)。 - 启动时 PD(内嵌 etcd)尝试从快照恢复:先成功恢复了 v2 store 快照(
recovered v2 store from snapshot,索引1671816728),但对应的 v3 backend 快照文件(0000000063a5e618.snap.db)在磁盘上不存在。 - 由于 v3 快照缺失,etcd 在
NewServer阶段触发PANIC,进而引发空指针异常(invalid memory address or nil pointer dereference),最终FATAL退出。
根因:该 PD 节点在异常关闭(主机故障 / 磁盘问题)后,元数据不一致——v2 快照保留但 v3 快照文件丢失,导致无法从快照恢复。
处置思路:由于另外两个 PD 节点(182、183)正常运行且构成多数派,181 节点可以移除损坏数据目录后重新加入集群,通过 Raft 从其他节点同步数据。
操作如下:
# 1. 停止可能卡住的故障进程
kill -9 <pd_pid>
# 2. 备份并清理损坏的数据目录(mv 改名保留,而非直接删除)
mv /tidb-data/pd-2379 /tidb-data/pd-2379_bak
# 3. 重新启动 PD 服务
systemctl start pd-2379
2.3 第三阶段:etcd 成员已存在,启动冲突
清理数据目录后重启,PD 仍然 FATAL,日志变了:
[2026/07/11 04:26:23.122 +08:00] [INFO] [backend.go:80] ["opened backend db"]
[path=/tidb-data/pd-2379/member/snap/db]
[2026/07/11 04:26:23.123 +08:00] [INFO] [etcd.go:369] ["closing etcd server"]
[2026/07/11 04:26:23.123 +08:00] [FATAL] [main.go:117] ["run server failed"]
[error="[PD:etcd:ErrStartEtcd]member 6f3bc6d97910272 has already been bootstrapped:
member 6f3bc6d97910272 has already been bootstrapped"]
分析:
- 这次日志显示
member-initialized=false,且initial-cluster仍指向三个节点、initial-cluster-state=new。 - 错误信息
member 6f3bc6d97910272 has already been bootstrapped说明:181 节点之前已经作为 etcd 成员被引导过,它的成员 ID(6f3bc6d97910272)仍然记录在存活的 etcd 集群中(从 182、183 同步来的成员列表)。 - 但本地数据目录已被清空,etcd 又以"新集群"方式(
initial-cluster-state=new)尝试启动,二者冲突——成员 ID 已存在却拿不到本地数据,于是 FATAL。
处置思路:需要先从存活的 PD 集群中删除 181 这个成员,再用 --join 方式以新成员身份加入。
# 1. 先停掉不停重启的 PD 服务(否则会反复 crash-loop)
systemctl stop pd-2379
# 2. 通过存活的 PD(183)删除 181 成员
tiup ctl:v6.5.0 pd -u http://xxx.183:2379 member delete name pd-xxx.181-2379
# 3. 确认成员已删除(此时应只剩 182、183)
tiup ctl:v6.5.0 pd -u http://xxx.183:2379 member
确认删除后,以 --join 方式将 181 重新加入集群(指向存活的 182):
nohup /tidb-deploy/pd-2379/bin/pd-server \
--name=pd-xxx.181-2379 \
--data-dir=/tidb-data/pd-2379 \
--advertise-client-urls=http://xxx.181:2379 \
--advertise-peer-urls=http://xxx.181:2380 \
--join=http://xxx.182:2379 \
--log-file=/tidb-deploy/pd-2379/log/pd.log >> /tidb-deploy/pd-2379/log/nohup.out 2>&1 &
2.4 第四阶段:数据目录权限拒绝
--join 启动后又报新错:
[2026/07/11 05:53:39.080 +08:00] [FATAL] [main.go:117] ["run server failed"]
[error="[PD:etcd:ErrStartEtcd]cannot access data directory:
open /tidb-data/pd-2379/.touch: permission denied"]
分析:之前 mv 备份旧目录后,新创建的 /tidb-data/pd-2379 目录属主 / 权限不对,PD 进程(以 tidb 用户运行)无法写入。
处置:重建目录并修正属主与权限:
mkdir -p /tidb-data/pd-2379
chown -R tidb:tidb /tidb-data/pd-2379
chmod 755 /tidb-data/pd-2379
# 删掉上一次 join 产生的残留数据,重新执行 join 步骤
清理后重新执行 2.3 的 --join 启动命令。
2.5 第五阶段:元数据同步超时,但最终恢复
重新 join 后日志出现告警:
[2026/07/11 05:44:34.948 +08:00] [INFO] [node.go:327]
["raft.node: 7f92c1206e32fe36 elected leader 6f5ed2dad4709252 at term 34"]
[2026/07/11 05:44:45.863 +08:00] [WARN] [server.go:2098]
["failed to publish local member to cluster through raft"]
[local-member-id=7f92c1206e32fe36]
[request-path=/0/members/7f92c1206e32fe36/attributes]
[publish-timeout=11s]
[error="etcdserver: request timed out, possibly due to connection lost"]
# 同类 WARN 持续出现多次...
分析:
elected leader 6f5ed2dad4709252 at term 34说明 181 节点已成功参与 Raft 选举,集群已经接受 181 成为成员。- 但
failed to publish local member表示在向集群发布本节点属性(名称、ClientURLs)时出现超时。这通常是网络抖动或节点正在从 leader 同步大量元数据、负载较高所致,属于临时性问题而非致命错误。
处置:进程一度卡住,重启 PD 服务后恢复正常。验证:
curl http://xxx.181:2379/version
# 返回正常版本信息,说明 PD 接口可用
tiup ctl:v6.5.0 pd -u http://xxx.183:2379 member
# 成员列表中重新出现 181 节点
PD 节点恢复完成。
三、TiKV 节点恢复
PD 恢复后,发现同主机上的 TiKV 节点(xxx.181:20160,数据目录 /data1/tikv-20160)也无法启动。
3.1 故障现象:SST 文件比 WAL 更新
TiKV 日志关键片段:
[2026/07/11 05:57:24.391 +08:00] [WARN] [config.rs:907] ["not on SSD device"]
[data_path=/data1/tikv-20160]
[2026/07/11 05:57:27.722 +08:00] [ERROR] [engine_factory.rs:164] ["failed to create kv engine"]
[err="Engine(Status { code: IoError, ..., state: \"Corruption: SST file is ahead of WALs in CF lock\" })"]
[path=/data1/tikv-20160/db]
[2026/07/11 05:57:27.722 +08:00] [FATAL] [server.rs:1828]
["failed to create kv engine: Storage Engine Status { ..., state: \"Corruption: SST file is ahead of WALs in CF lock\" }"]
日志中还伴随若干系统参数告警(非致命,但值得关注):
[WARN] kernel parameters net.core.somaxconn got 128, expect 32768
[WARN] kernel parameters net.ipv4.tcp_syncookies got 1, expect 0
[WARN] kernel parameters vm.swappiness got 60, expect 0
[WARN] memory_usage_limit > total, fallback to total
[WARN] memory_usage_limit > recommanded, maybe page cache isn't enough
[WARN] not on SSD device # 数据目录未落在 SSD 上
分析:
- 核心错误
Corruption: SST file is ahead of WALs in CF lock表示 lock 列族的 SST 文件比预写日志(WAL)更新。正常情况下 WAL 是写入的源头,SST 由 WAL + MemTable flush 产生,SST 不应"领先"于 WAL。 - 这种不一致通常由 异常断电、磁盘故障或写缓存未刷盘 导致——主机掉电时 RocksDB 还没把 WAL 刷盘,但 SST(或文件系统元数据)已经部分落盘,重启后引擎一致性校验失败,拒绝启动。
- 日志里
not on SSD device与memory_usage_limit > total提示该存储环境本身存在隐患(非 SSD、内存配置偏高),是后续需要优化的方向。
处置思路:TiKV 数据已损坏无法就地修复,且该节点数据可由其他副本恢复(max-replicas=3)。因此采取 从 PD 中删除该 store → 清空本地数据目录 → 以新节点身份重新加入 的方式重建。
3.2 第二阶段:停止 TiKV 并从 PD 删除故障 store
# 1. 停止 TiKV 进程
ps -ef | grep tikv-server | grep 181 | grep -v grep | awk '{print $2}' | xargs kill -9
# 2. 查看所有 store 状态,找到故障 store 的 ID
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 store
查询结果中故障 store 的 ID 为 4。直接尝试删除:
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 store delete 4
此时遇到一个问题:由于集群 max-replicas=3,而当前可用的 TiKV 节点不足以在删除该 store 后仍满足 3 副本,PD 拒绝执行删除(无法安全下线)。需要临时将副本数降为 2,待节点重建恢复后再调回 3:
# 1. 临时降低副本数,解除删除限制
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 config set max-replicas 2
# 2. 再次删除故障 store
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 store delete 4
# 3. 节点重建成功后,恢复副本数
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 config set max-replicas 3
注意:将
max-replicas临时降为 2 会降低数据冗余度,仅在确认其余副本健康、且能尽快完成重建的窗口期内使用。操作完成后务必第一时间恢复。
3.3 第三阶段:清空数据目录并重新启动 TiKV
# 1. 清理损坏的数据目录并重建
mkdir -p /data1/tikv-20160/db
mkdir -p /data1/tikv-20160/raft-engine
# 2. 以新节点身份启动 TiKV(指向全部 PD 节点)
nohup /tidb-deploy/tikv-20160/bin/tikv-server \
--addr 0.0.0.0:20160 \
--advertise-addr xxx.181:20160 \
--status-addr 0.0.0.0:20180 \
--advertise-status-addr xxx.181:20180 \
--pd xxx.181:2379,xxx.182:2379,xxx.183:2379 \
--data-dir /data1/tikv-20160 \
--config conf/tikv.toml \
--log-file /tidb-deploy/tikv-20160/log/tikv.log >> /tidb-deploy/tikv-20160/log/nohup.out 2>&1 &
启动后持续观察 tikv.log,确认无 Corruption / FATAL,Raft 正常接入集群,Region 副本开始通过 Raft 同步补齐。
3.4 验证恢复
tiup cluster display tidb-prod
tiup ctl:v6.5.0 pd -u http://xxx.182:2379 store
所有节点状态恢复正常,store 列表中 181 节点重新上线,副本数恢复为 3。TiKV 节点恢复完成,集群整体恢复正常。
四、故障根因与经验总结
4.1 根因
本次故障的根本原因是宿主机异常关闭(掉电 / 硬件故障),导致:
- PD(etcd)层面:etcd 的 v2 快照保留,但 v3 backend 快照文件(
.snap.db)丢失或未完整落盘,启动时无法从快照恢复,触发 PANIC。 - TiKV(RocksDB)层面:lock 列族的 SST 文件领先于 WAL,说明写缓存未及时刷盘(磁盘掉电保护不足 / 文件系统未正常卸载),RocksDB 一致性校验失败。
两类问题的本质都是 异常关机导致存储引擎的持久化数据不一致。
4.2 关键操作要点
- PD 数据目录损坏但多数派存活时:不要直接删数据目录硬拉,应先从存活集群
member delete移除故障成员,再以--join方式以新成员身份重新加入,让 Raft 自动同步数据。 initial-cluster-state=new与已存在成员冲突:这是 etcd 的典型坑——成员 ID 已在集群中注册,本地却无数据。必须先删成员再 join。- 删除 store 受副本数限制:当可用节点不足以满足
max-replicas时,PD 会拒绝下线。临时降副本数是可行的,但务必在重建后立即恢复。 - 操作前先备份:即使是"坏了"的数据目录,也用
mv改名保留而非rm删除,保留回溯余地。 - 权限问题不可忽视:重建目录后要
chown回tidb用户,否则进程无法写入.touch等文件。 - 临时性 WARN 不必慌:如
failed to publish local member ... request timed out,在节点刚加入、正在同步元数据时属于正常现象,可观察后重启确认。
五、附:核心命令速查
# === PD 相关 ===
# 查看集群状态
tiup cluster display tidb-prod
# 查看成员
tiup ctl:v6.5.0 pd -u http://<存活PD>:2379 member
# 删除故障成员(按 name)
tiup ctl:v6.5.0 pd -u http://<存活PD>:2379 member delete name pd-<IP>-2379
# 以 join 方式启动 PD
nohup /tidb-deploy/pd-2379/bin/pd-server \
--name=pd-<IP>-2379 \
--data-dir=/tidb-data/pd-2379 \
--advertise-client-urls=http://<IP>:2379 \
--advertise-peer-urls=http://<IP>:2380 \
--join=http://<存活PD>:2379 \
--log-file=/tidb-deploy/pd-2379/log/pd.log >> /tidb-deploy/pd-2379/log/nohup.out 2>&1 &
# === TiKV 相关 ===
# 查看 store
tiup ctl:v6.5.0 pd -u http://<存活PD>:2379 store
# 临时调副本数
tiup ctl:v6.5.0 pd -u http://<存活PD>:2379 config set max-replicas 2
# 删除故障 store
tiup ctl:v6.5.0 pd -u http://<存活PD>:2379 store delete <store_id>
# 启动 TiKV
nohup /tidb-deploy/tikv-20160/bin/tikv-server \
--addr 0.0.0.0:20160 \
--advertise-addr <IP>:20160 \
--status-addr 0.0.0.0:20180 \
--advertise-status-addr <IP>:20180 \
--pd <PD1>:2379,<PD2>:2379,<PD3>:2379 \
--data-dir /data1/tikv-20160 \
--config conf/tikv.toml \
--log-file /tidb-deploy/tikv-20160/log/tikv.log >> /tidb-deploy/tikv-20160/log/nohup.out 2>&1 &
本文为生产故障复盘记录,命令与日志均来自实际处置过程。操作涉及数据目录清理与副本数变更,请在测试环境验证并做好备份后再在生产环境执行。注意:内容由AI辅助生成,可能出现部分错误仅供参考。