停电后 Redis 集群两节点起不来:fix 完还报 Bad file format?多部分 AOF 修复的正确姿势

测试服务器断电恢复后,6 节点 Redis 集群挂了俩,应用启动直接 Redisson Not all slots covered

拿出 redis-check-aof --fix 一通操作,结果发现:fix 跑完了,Redis 启动还在报 Bad file format。是工具骗人?还是我们用错了?

更扎心的是------本来要修两个节点,最后才发现其中一个根本不需要修,5 秒就能从主节点同步回来。绕了一大圈白忙一场。


一、问题现象

1.1 应用启动直接挂

凌晨断电,运维通电后,应用容器一启动就连环报错:

复制代码
Caused by: org.redisson.client.RedisConnectionException:
  Not all slots covered! Only 10923 slots are available.
  Set checkSlotsCoverage = false to avoid this check.
  Failed masters according to cluster status:
  [redis://redis-master.example.com:6381]
        at org.redisson.cluster.ClusterConnectionManager.<init>
        ...

Redis Cluster 共 16384 个 slot,现在只有 10923 个------少了一整个主节点的份额(5461 个 slot)。Redisson 校验失败,应用直接 boot 不起来。

1.2 看一眼 Redis 节点

sh 复制代码
$ docker ps -a
CONTAINER ID   IMAGE         STATUS                          NAMES
f0431a69d5b9   redis:7.4.2   Restarting (1) 38 seconds ago   redis6
c0d1df1e49ac   redis:7.4.2   Up 11 minutes                   redis5
2e67ec55c5ee   redis:7.4.2   Up 11 minutes                   redis4
3c887dba5270   redis:7.4.2   Restarting (1) 32 seconds ago   redis3
7be6397cfa68   redis:7.4.2   Up 11 minutes                   redis2
d62fdb47ac23   redis:7.4.2   Up 11 minutes                   redis1

6 个节点里 redis3、redis6 在 Restarting 循环 ,其他 4 个正常。--restart=always 在拼命拉起,但拉一次崩一次。

📌 环境说明 :本次故障环境是 测试环境的 Redis 7.4.2 集群 ,3 主 3 从、Docker 部署,搭建流程和生产保持一致------具体怎么从 0 搭一套带可视化、监控、告警、看板的 Redis 集群,可以看我之前那篇 Redis 集群从裸奔到全副武装:搭建、可视化、监控、告警、看板一条龙。本文里出现的容器名(redis1~redis6)、端口(6379~6384、16379~16384)、目录布局(/data/redis-cluster/redisN/)都来自那套部署方案。


二、排查现场

2.1 容器日志

log 复制代码
1:M 28 Apr 02:40:33.110 * Reading RDB base file on AOF loading...
1:M 28 Apr 02:40:33.133 * Done loading RDB, keys loaded: 26000+
1:M 28 Apr 02:40:33.334 # Bad file format reading the append only
   file appendonly.aof.271.incr.aof: make a backup of your AOF file,
   then use ./redis-check-aof --fix <filename.manifest>

错误很直接------appendonly.aof.271.incr.aof 文件格式坏了 。Redis 自己也给了药方:redis-check-aof --fix

但这里有个坑:它让你 fix 的不是 .aof 文件,是 <filename.manifest>

为什么?这就要先讲下 Redis 7 的 AOF 格式。


三、知识铺垫:Redis 7 的多部分 AOF

3.1 老式单 AOF vs 新式多部分 AOF

Redis 7.0 之前,AOF 就是一个单文件 appendonly.aof,每条写命令往后追加。

Redis 7.0 改了------AOF 拆成 三类文件

文件 作用 示例
base 上一次 AOF rewrite 之后的全量快照(RDB 格式) appendonly.aof.271.base.rdb
incr base 之后的增量写命令(追加) appendonly.aof.271.incr.aof
manifest 索引文件,登记 base 和 incr 的清单 appendonly.aof.manifest

manifest 文件长这样:

sh 复制代码
$ cat /data/redis-cluster/redis3/appendonlydir/appendonly.aof.manifest
file appendonly.aof.271.base.rdb seq 271 type b
file appendonly.aof.271.incr.aof seq 271 type i

每行一条登记:「文件名 序号 类型」。Redis 启动时按 manifest 找对应文件。

3.2 为什么停电会坏 incr.aof

Redis 默认 appendfsync everysec:每秒 fsync 一次。意味着断电时最多丢 1 秒的写入------这是大家都知道的。

但更要命的是另一种情况:正在写到一半的命令 。当 Redis 把写命令往 incr.aof 追加时,操作系统可能还没把这几百字节落盘。突然断电,磁盘上只剩半截命令

启动时 Redis 严格校验,发现 incr.aof 末尾不是完整命令,直接拒绝加载------就是日志里那个 Bad file format

3.3 fix 必须给 manifest 而不是 .aof

很多搜出来的中文文章会告诉你:

bash 复制代码
# ❌ 老办法,Redis 7 不再适用
redis-check-aof --fix /data/appendonly.aof

这是 Redis 6 及之前的写法。Redis 7+ 多部分 AOF 必须给 manifest

bash 复制代码
# ✅ 正确姿势
redis-check-aof --fix /data/appendonlydir/appendonly.aof.manifest

为什么?因为 fix 工具要:

  1. 校验 base.rdb(基础快照)
  2. 校验 incr.aof(增量)
  3. 截断坏掉的字节,同时更新 manifest 反映新状态

如果你只 fix 单个 .aof 文件,manifest 没更新,Redis 启动时拿到的还是旧记录,照样炸。

Redis 7:我已经不是当年那个 Redis 6 了。

你:......好的我重新读文档。


四、解决方案:修主节点 redis3

4.1 备份 → fix → 重启

整个流程很标准:

bash 复制代码
# 1. 看一眼当前 AOF 目录
$ ls -lh /data/redis-cluster/redis3/appendonlydir/
-rw------- 1 lxd docker 3.5M  appendonly.aof.271.base.rdb
-rw------- 1 lxd docker  42M  appendonly.aof.271.incr.aof
-rw------- 1 lxd docker   96  appendonly.aof.manifest

# 2. 停容器(不要靠 --restart=always 自然停掉,会干扰诊断,下面会说)
$ docker stop redis3

# 3. 备份整个目录(出问题能回滚)
$ cp -r /data/redis-cluster/redis3/appendonlydir \
        /data/redis-cluster/redis3/appendonlydir.bak.$(date +%Y%m%d)

# 4. 用临时容器跑 fix ------ 关键是给 manifest 文件
$ docker run --rm -it \
    -v /data/redis-cluster/redis3:/data \
    -w /data \
    redis:7.4.2 \
    redis-check-aof --fix /data/appendonlydir/appendonly.aof.manifest

fix 输出(节选关键行):

复制代码
Start checking Multi Part AOF
[offset 3598345] \o/ RDB looks OK! \o/
BASE AOF appendonly.aof.271.base.rdb is valid
Start to check INCR files.
AOF appendonly.aof.271.incr.aof format error
AOF analyzed: filename=...incr.aof, size=44027146, ok_up_to=44026973, diff=173
This will shrink the AOF appendonly.aof.271.incr.aof from 44027146 bytes,
   with 173 bytes, to 44026973 bytes
Continue? [y/N]: y
Successfully truncated AOF appendonly.aof.271.incr.aof
All AOF files and manifest are valid

工具的逻辑很清晰:

步骤 输出关键词 作用
1. 校验 base BASE AOF ... is valid 基础快照没坏(断电前已经完整落盘的)
2. 校验 incr format error + ok_up_to=44026973 找到末尾损坏的 173 字节
3. 截断 + 更新 manifest Successfully truncated + All AOF files and manifest are valid 切掉坏字节,manifest 同步更新

被截断的 173 字节就是断电时正在写的那条半截命令,属于"丢失最多 1 秒数据"的预期范围。

4.2 实战踩坑:fix 完启动还报错?

按理说 fix 完事就完了,但实际启动后日志是这样:

log 复制代码
02:46:52  fix 后第一次启动 → Bad file format(又来??)
02:47:52  容器自动重启   → Bad file format
02:49:23  再启动一次     → 终于成功,「DB loaded from incr file ... 0.201s」

中间一度怀疑工具是不是骗人。后来确认:这中间又跑了一次相同的 fix 命令才稳。

具体原因没深究------可能是页面缓存、可能是 docker 容器残留的文件句柄、也可能是第一次输入 y 时刚好和容器自动重启抢资源。但有一条铁律值得记下来:

⚠️ 修复 AOF 之前,先关掉容器的自动重启

bash 复制代码
docker update --restart=no redis3

等修好、确认能稳定启动后再改回来:

bash 复制代码
docker update --restart=always redis3

否则你 stop 完容器准备 fix,--restart=always 在你打字的时候就把它拉回来了;fix 完启动失败,又被自动拉一遍循环失败的日志。最后你都分不清当前看的是修复前还是修复后的日志。

4.3 验证

log 复制代码
02:49:23.694 * Done loading RDB, keys loaded: 26000+
02:49:23.895 * DB loaded from incr file appendonly.aof.271.incr.aof: 0.201s
02:49:23.895 * DB loaded from append only file: 0.225s
02:49:23.896 * Ready to accept connections tcp
02:49:25.948 * Cluster state changed: ok

Cluster state changed: ok------主节点恢复了。


五、剧情反转:redis6 其实根本不用修

到这里 redis3 修好了,按理说轮到 redis6。问题完全一样的报错(appendonly.aof.274.incr.aof Bad file format),打算照搬流程再来一遍。

我们也确实老老实实再 fix 了一遍 redis6 的 AOF。

但启动验证日志里有意思的事情发生了------

5.1 一行日志暴露的真相

redis6 启动成功后,日志接着跑出这样几行:

log 复制代码
1:S * Before turning into a replica, using my own master parameters
     to synthesize a cached master ...
1:S * Connecting to MASTER redis-master.example.com:6381
1:S * MASTER <-> REPLICA sync started
1:S * Full resync from master: f124e7992cf0265dc3a8ef6ddbfad35fbcfc8064
1:S * MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF
1:S * Done loading RDB, keys loaded: 27000+
1:S * MASTER <-> REPLICA sync: Finished with success
1:S * Removing the history file appendonly.aof.274.incr.aof in the background

注意第二条:Connecting to MASTER。第一列前缀也变成了 1:S(Slave)。

redis6 是 redis3 的从节点。

并且,启动起来后它主动从主节点全量同步了一份新数据 ------5 秒内完成,27000+ 个 key 重新就位。本地 incr.aof 那 1 秒的损失?根本没用上,全被 master 推过来的 RDB 覆盖了。最后一行甚至直接把刚 fix 好的 incr.aof.274 文件删了。

也就是说------刚才花在 redis6 上的 fix 命令,白做了

5.2 主从节点的修复策略完全不一样

复盘下来,Cluster 中的主从节点,AOF 损坏的处理方式应该是两套:

节点类型 数据是否唯一 推荐方案 耗时
主节点 ✅ 是(损坏=丢数据) redis-check-aof --fix manifest 看 incr.aof 大小,几秒到几分钟
从节点 ❌ 不是(主节点有副本) 直接清空 appendonlydir,重启走 full resync 看数据量,本次 5 秒

从节点的"清盘重建"流程:

bash 复制代码
# 1. 关掉自动重启
$ docker update --restart=no redis6

# 2. 停容器
$ docker stop redis6

# 3. 备份 + 清空(清空是为了让 Redis 启动时找不到本地 AOF,触发 full resync)
$ mv /data/redis-cluster/redis6/appendonlydir \
     /data/redis-cluster/redis6/appendonlydir.bak.$(date +%Y%m%d)

# 4. 启动,让它从主节点全量同步
$ docker start redis6
$ docker logs -f redis6   # 看到 "Full resync from master" 就稳了

# 5. 恢复自动重启
$ docker update --restart=always redis6

整个流程比 fix 还简单------不用进容器、不用交互式输 y、5 秒同步完事。

5.3 但前提是:你得知道哪台是主、哪台是从

主从拓扑判断有两条路。

方法一:cluster nodes 命令

bash 复制代码
$ docker exec redis1 redis-cli -p 6379 -a '<your-pass>' cluster nodes
ff8a4404... redis-master.example.com:6381@16381 master - ... connected 5461-10922
d9a06325... redis-replica.example.com:6384@16384 slave ff8a4404... ... connected
...

master / slave 标志一目了然。第二列还能看到 slave 跟随的 master ID。

方法二:看启动日志

log 复制代码
1:S ... Connecting to MASTER redis-master.example.com:6381

Connecting to MASTER 的就是从节点。日志行首的 1:M(Master)和 1:S(Slave)前缀也是关键提示。

redis6:你修我?没必要,主节点已经把活干了。


六、举一反三

6.1 完整决策树

下次遇到类似断电故障,按这个流程走:

复制代码
Redis 启动失败,日志报 Bad file format
        │
        ├─▶ ① 先关掉 --restart=always
        │      docker update --restart=no <container>
        │
        ├─▶ ② 判断节点类型
        │      cluster nodes 命令 / 启动日志 1:M / 1:S
        │
        ├─▶ 主节点?
        │      ├─ 备份 appendonlydir
        │      ├─ redis-check-aof --fix .../appendonly.aof.manifest
        │      ├─ 重启验证「Cluster state changed: ok」
        │      └─ 恢复 --restart=always
        │
        └─▶ 从节点?
               ├─ 备份并清空 appendonlydir
               ├─ 重启走 full resync
               ├─ 验证「Connecting to MASTER」+「sync: Finished with success」
               └─ 恢复 --restart=always

6.2 几个易错点汇总

易错点 错误做法 正确做法
给 fix 工具的文件 redis-check-aof --fix appendonly.aof redis-check-aof --fix appendonly.aof.manifest
修复前没关 restart 策略 直接 fix,docker 在循环拉起 docker update --restart=no
从节点也跑 fix 浪费时间,且本地数据最终会被 master 覆盖 直接清盘走 full resync
没备份就 fix 工具截断不可逆,没法回滚 必须 cp -r appendonlydir appendonlydir.bak.YYYYMMDD

6.3 预防:怎么减少下次断电的损失

  • 物理层:UPS 给关键服务器兜底------便宜,能买几分钟优雅关机时间
  • 宿主机层vm.overcommit_memory=1(Redis 启动日志一直在喊,别忽略)
  • Redis 配置层 :业务完全不能容忍丢任何数据 ,把 appendfsynceverysec 改成 always------但写性能会有显著下降,权衡好再上
  • 拓扑层:每个主节点都至少配 1 个从节点。本次 redis6 能"白嫖"主节点同步,就是因为有副本------没副本的话,主节点 fix 失败你就只能跑路了

七、总结

这次故障最大的收获其实不是「学会了 redis-check-aof」------

而是想明白了一件事:Redis Cluster 中,主从节点是两条修复路线。手里只有锤子的时候,看什么问题都像钉子;多一把工具,就多一种思路。

回头看:

  • 主节点redis-check-aof --fix manifest,几分钟搞定,关键是别忘了 fix 的是 manifest 不是 .aof
  • 从节点:清盘 + 重启 + 让 master 推,5 秒搞定,还更稳

下次再断电,别一上来就 fix 全场------先看看哪些是从节点,能少做就少做。


有用的话点个收藏。如果你也踩过 Redis 7 多部分 AOF 的坑,欢迎在评论区交流。

相关推荐
接着奏乐接着舞1 小时前
3D Tiles tileset.jso 数据格式
运维·服务器·3d
李小白202002021 小时前
RK3568 linux6.1 死机
linux·运维·服务器
杨云龙UP1 小时前
Oracle数据库启动失败:ORA-29701、ORA-01565、ORA-17503故障处理记录_20260429
linux·运维·数据库·oracle·centos
Agent产品评测局2 小时前
离散制造业生产流程优化,AI落地实操步骤详解:从传统自动化到企业级智能体的技术范式跃迁
运维·人工智能·ai·自动化
Gary Studio2 小时前
ubuntu 16.04一键换源
linux·运维·ubuntu
又来敲代码了2 小时前
k8s的部署
linux·运维·云原生·容器·kubernetes
YaBingSec2 小时前
玄机网络安全靶场:Hadoop YARN ResourceManager 未授权 RCE WP
大数据·数据库·hadoop·redis·笔记·分布式·web安全
梦·D·2 小时前
安全运维工具箱sskit_v1.0.3 部署
运维
qq_40999093?2 小时前
NoSQL数据库解析:Redis
数据库·redis·nosql