0
1
1
0
博客/.../

TiDB 集群主机故障后的 PD 与 TiKV 节点恢复实战

 TIDB_逸昂  发表于  2026-07-30

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 文件损坏 两类错误。

整个恢复过程分为两个阶段:

  1. PD 节点恢复:经历了快照损坏 → etcd 成员冲突 → 权限拒绝 → 元数据同步超时 共四个问题;
  2. 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 根因

本次故障的根本原因是宿主机异常关闭(掉电 / 硬件故障),导致:

  1. PD(etcd)层面:etcd 的 v2 快照保留,但 v3 backend 快照文件(.snap.db)丢失或未完整落盘,启动时无法从快照恢复,触发 PANIC。
  2. TiKV(RocksDB)层面:lock 列族的 SST 文件领先于 WAL,说明写缓存未及时刷盘(磁盘掉电保护不足 / 文件系统未正常卸载),RocksDB 一致性校验失败。

两类问题的本质都是 异常关机导致存储引擎的持久化数据不一致

4.2 关键操作要点

  1. PD 数据目录损坏但多数派存活时:不要直接删数据目录硬拉,应先从存活集群 member delete 移除故障成员,再以 --join 方式以新成员身份重新加入,让 Raft 自动同步数据。
  2. initial-cluster-state=new 与已存在成员冲突:这是 etcd 的典型坑——成员 ID 已在集群中注册,本地却无数据。必须先删成员再 join。
  3. 删除 store 受副本数限制:当可用节点不足以满足 max-replicas 时,PD 会拒绝下线。临时降副本数是可行的,但务必在重建后立即恢复。
  4. 操作前先备份:即使是"坏了"的数据目录,也用 mv 改名保留而非 rm 删除,保留回溯余地。
  5. 权限问题不可忽视:重建目录后要 chown 回 tidb 用户,否则进程无法写入 .touch 等文件。
  6. 临时性 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辅助生成,可能出现部分错误仅供参考。

0
1
1
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论