bigywater.tech
Redis · 84

Redis Cluster 故障恢复

一个分片的主库和从库同时挂了怎么办?这比「主库挂了从库接手」严重得多:那一片数据在集群里已经没有副本了。这篇复现这个最坏情形,看三种结局:数据在磁盘上→重启找回;没有持久化→集群「恢复正常」却悄悄丢了三分之一的数据;放弃数据→用 fix 强行让集群可用。

5.0 s
同时杀掉一个分片的主从后,cluster_state 变 fail(默认 require-full-coverage yes,连幸存分片也拒绝读写)
0 vs 9998
重启主库后 30000 个 key 里读不到的个数:有 AOF vs 没有持久化
cluster_state 会说谎
没有持久化时重启主库,集群 2 秒就变 ok,但那一片数据已经没了

事故一:主从一起挂,默认配置

实测 · 本机执行输出
  被杀:主 7560 + 从 7564;幸存节点上 cluster_state 变化(相对 kill 的秒数):{'ok': 0.0, 'fail': 4.99}
  在幸存主库 7561 上写它自己负责的槽里的 key: CLUSTERDOWN The cluster is down  读 key: CLUSTERDOWN The cluster is down
  向幸存主库请求已丢失分片的 key k:3: CLUSTERDOWN The cluster is down
  只重启被杀的主库(AOF 加载):3.18 s 后整个集群 cluster_state = ok
  数据校验(30000 个 key):读不到/值不对 0 个,出错 0 个

杀掉分片 X 的主库和从库后,幸存节点在 5.0 s 后把 cluster_state 置为 fail。之后无论读写,哪怕是完全属于幸存分片的 key,都会得到 CLUSTERDOWN The cluster is down:默认 cluster-require-full-coverage yes 要求 16384 个槽都有主库,少一个就全体拒绝服务。

恢复:只重启被杀的主库(nodes.conf 在,AOF 在):3.2 s 后整个集群回到 ok,30000 个 key 校验丢失 0 个(这里 AOF 用了 appendfsync always;默认 everysec 下崩溃时可能丢最近约 1 秒——这一点本文没有实测)。

事故二:同样的事故,没有持久化

实测 · 本机执行输出
  重启主库后 2.05 s cluster_state = ok(nodes.conf 还在,槽位归属恢复)
  数据校验(30000 个 key):读不到 9998 个(33.3%),出错 0 个——集群「恢复正常」,但这一片数据没了

没有 AOF、没有 RDB 时,重启的主库带着旧的 nodes.conf(槽位归属)上线,但数据是空的。集群 2.1 s 就回到 ok,看起来完全正常——但读 30000 个 key,9998 个读不到(33.3%,恰好是那个分片)。没有任何告警:cluster_state 是 ok,只是那一片数据没有了。这也是集群里「主库必须有持久化,或者至少有从库」的原因:从库是第二份副本,持久化是第三份。

如果不想全体停摆

实测 · 本机执行输出
  幸存节点 cluster_state 变化:{'ok': 0.0};CLUSTER INFO 里 cluster_slots_ok / fail: 10923 / 5461   cluster_state = ok
  写幸存分片的 key: OK  读: w
  请求已丢失分片里的 key k:3: MOVED 2036 127.0.0.1:7580

cluster-require-full-coverage no 让集群在有槽没人负责时仍然工作:幸存节点 cluster_state = ok,cluster_slots_ok / cluster_slots_fail = 10923 / 5461,写幸存分片的 key 成功。请求丢失分片的 key,幸存节点回 MOVED 指向那个已经死掉的主库——客户端会连不上、超时。这是「部分可用」与「全体可用」的取舍。

放弃这片数据:redis-cli --cluster fix

实测 · 本机执行输出
  fix 输出(关键行):
    [OK] 20002 keys in 2 masters.
    [OK] All nodes agree about slots configuration.
    [ERR] Not all 16384 slots are covered by nodes.
    >>> Fixing slots coverage...
    The following uncovered slots have no keys across the cluster:
    Fix these slots by covering with a random node? (type 'yes' to accept): >>> Covering slot 576 with 127.0.0.1:7582
    >>> Covering slot 1531 with 127.0.0.1:7582
    >>> Covering slot 2691 with 127.0.0.1:7582
    >>> Covering slot 1551 with 127.0.0.1:7582
    >>> Covering slot 4055 with 127.0.0.1:7582
  之后 cluster_state = ok  cluster_slots_ok = 16384
  数据校验(前 6000 个 key):读不到 2008 个,出错 1 个(那一片数据永久丢失,槽位被分配给了存活的主库)

如果那个分片确实回不来了,又想让集群恢复完整,redis-cli --cluster fix 可以把没人负责的槽分给存活的主库。它对「有不可达主库」的情形要求额外的 --cluster-fix-with-unreachable-masters 选项,并且提示这可能导致数据丢失;确认后逐个槽位 Covering slot … with 127.0.0.1:7582。结果:cluster_state 回到 ok,cluster_slots_ok = 16384,但那片数据永久丢了(前 6000 个 key 里 2008 个读不到)。

分步演示:三种结局

Redis Cluster 故障恢复
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。3 主 3 从;每个分片只有 1 个从库;kill -9 同时杀主从;30000 个 key 每个 key 校验一次(fix 之后只校验前 6000 个,因为丢失分片的请求会很慢)
    • 没有验证:AOF everysec 下崩溃的实际丢失量、断电场景、只有从库存活时的手工 CLUSTER FAILOVER TAKEOVER、CLUSTER SETSLOT … NODE / ADDSLOTS 手工重新分配、Redis 8 之前版本 fix 的行为
    京ICP备2026067502号-1京公网安备11010502063487号