测试服务器断电恢复后,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 工具要:
- 校验
base.rdb(基础快照) - 校验
incr.aof(增量) - 截断坏掉的字节,同时更新 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 之前,先关掉容器的自动重启:
bashdocker update --restart=no redis3等修好、确认能稳定启动后再改回来:
bashdocker 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 配置层 :业务完全不能容忍丢任何数据 ,把
appendfsync从everysec改成always------但写性能会有显著下降,权衡好再上 - 拓扑层:每个主节点都至少配 1 个从节点。本次 redis6 能"白嫖"主节点同步,就是因为有副本------没副本的话,主节点 fix 失败你就只能跑路了
七、总结
这次故障最大的收获其实不是「学会了 redis-check-aof」------
而是想明白了一件事:Redis Cluster 中,主从节点是两条修复路线。手里只有锤子的时候,看什么问题都像钉子;多一把工具,就多一种思路。
回头看:
- 主节点 :
redis-check-aof --fix manifest,几分钟搞定,关键是别忘了 fix 的是 manifest 不是 .aof - 从节点:清盘 + 重启 + 让 master 推,5 秒搞定,还更稳
下次再断电,别一上来就 fix 全场------先看看哪些是从节点,能少做就少做。
有用的话点个收藏。如果你也踩过 Redis 7 多部分 AOF 的坑,欢迎在评论区交流。