我不会起名字322 · 后端 / 算法 / 数据库
文章目录
- [Redis 从 0 到 1(三十三):机器断电 Redis 起不来------AOF/RDB 损坏的 4 步抢救流程](#Redis 从 0 到 1(三十三):机器断电 Redis 起不来——AOF/RDB 损坏的 4 步抢救流程)
-
- 一、现象:一行报错,全系统停摆
- [二、4 步抢救流程(收藏点 1)](#二、4 步抢救流程(收藏点 1))
- [三、Go 实操:抢救后数据完整度核对(收藏点 2)](#三、Go 实操:抢救后数据完整度核对(收藏点 2))
- 四、命令清单
- [五、排查步骤:Redis 拒绝启动](#五、排查步骤:Redis 拒绝启动)
- 六、总结
Redis 从 0 到 1(三十三):机器断电 Redis 起不来------AOF/RDB 损坏的 4 步抢救流程
上一篇《Redis 从 0 到 1(三十二):Cluster 节点宕机,请求疯狂报 MOVED------Go 客户端 4 个适配点》我们稳住了拓扑变化,链接:https://blog.csdn.net/2501_92769340/article/details/167400645 。
本案最硬核:机房断电后 Redis 拒绝启动,日志只有一行
Bad file format reading the append only file------数据就在坏文件里,怎么救?
一、现象:一行报错,全系统停摆
断电恢复后,redis-server 启动即退出:
* Reading RDB preamble from AOF file...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
AOF 是命令日志(第 7 篇),断电瞬间写入缓冲区的半个命令、或文件尾部的残缺 MULTI 块,都会让启动时的"重放校验"直接失败------Redis 不敢猜你想说什么,宁可拒绝启动。这不是 Redis 脆,是它在保护你不启动一个数据不一致的实例。
二、4 步抢救流程(收藏点 1)
铁律:先备份,再动任何文件。--fix 的代价是丢弃无法解析的部分,原文件是唯一后悔药。
bash
# 第 1 步:备份(!)
cp /var/lib/redis/appendonly.aof /backup/appendonly.aof.$(date +%Y%m%d%H%M)
cp /var/lib/redis/dump.rdb /backup/dump.rdb.$(date +%Y%m%d%H%M) 2>/dev/null
# 第 2 步:修复 AOF(切掉文件中第一个不合法的位置之后的所有内容)
redis-check-aof --fix /var/lib/redis/appendonly.aof
# 输出:Successfully truncated AOF at offset 12345678
# 含义:12345678 之后的命令全部丢弃 ------ 丢了多少心里有数
# 第 3 步:校验 RDB(如果同时开了 RDB)
redis-check-rdb /var/lib/redis/dump.rdb
# 第 4 步:启动 + 数据完整度核对
redis-server /etc/redis/redis.conf
redis-cli INFO persistence # 确认 aof_last_write_status、rdb_last_bgsave_status 正常
决策分支(先想清楚再动手):
| 现场 | 决策 |
|---|---|
| AOF 损坏但 RDB 完好、且 RDB 够新 | 可以走 RDB 启动(关 AOF 或换 RDB 文件),丢数 = AOF 比 RDB 多出的那部分 |
| AOF/RDB 都损坏 | fix AOF 后启动,丢尾部;然后从业务对账/日志补数据 |
| 有从库且从库数据完整 | 别修了,直接从库重建(第 31 篇思路反向用) |
| 开了 aof-use-rdb-preamble(混合持久化) | --fix 仍能处理,但截断点语义是"RDB 前缀之后的 AOF 部分",修复前确认版本支持 |
防大于治的两项配置:
conf
# redis.conf
aof-load-truncated yes # 默认 yes:尾巴截断的 AOF 自动忽略并启动(本次的事故就会被自动救回)
no-appendfsync-on-rewrite no # AOF 重写期间不暂停 fsync(保持默认,别为了性能开它)
注意:aof-load-truncated yes 能救"尾部残缺",救不了"中间损坏"(磁盘坏块)------中间坏了仍要 --fix 或从库重建。
三、Go 实操:抢救后数据完整度核对(收藏点 2)
修复丢的是"文件尾部",核对重点是尾部业务数据的连续性和计数对账:
go
package main
import (
"context"
"fmt"
"strconv"
"redis-0to1/redisx"
)
// 核对思路:业务写入时若用 INCR 维护序号(第 7 篇耐久性实验同款),
// 修复后比对 "最后写入序号" 与业务数据库的最后一条记录。
func main() {
rdb, _ := redisx.NewClient(redisx.Config{Addr: "127.0.0.1:6379", Password: "redis123", PoolSize: 10})
ctx := context.Background()
// 1. 全局序号对账
seq, err := rdb.Get(ctx, "order:seq").Result()
if err != nil {
fmt.Println("序号 key 不存在(丢得比想象多):", err)
return
}
fmt.Println("Redis 恢复到的最后序号:", seq)
// 2. 抽样验证序号连续性(修复截断点附近的 key 是否齐)
var n int
last, _ := strconv.Atoi(seq)
for i := last - 100; i <= last; i++ {
exists, _ := rdb.Exists(ctx, fmt.Sprintf("order:%d", i)).Result()
n += int(exists)
}
fmt.Printf("尾部 100 条订单存在 %d 条(缺口=%d,应去数据库回补)\n", n, 100-n)
// 3. 出报告:缺口区间写入恢复工单
}
产出物:丢数区间报告(截断偏移量 → 业务序号区间)→ 数据库回补 → 下游缓存重建。这一套是"断电事故 SOP"的收尾标准动作。
四、命令清单
| 命令 | 作用 |
|---|---|
redis-check-aof --fix <file> |
截断修复 AOF(丢弃不可解析部分) |
redis-check-rdb <file> |
校验 RDB 完整性 |
redis-check-aof --truncate-to-timestamp <ts> |
按时间点截断(精确保留某时刻前数据) |
INFO persistence |
启动后核对持久化状态 |
DEBUG POPULATE / DEBUG JMAP |
测试环境制造数据/调试(生产禁用 DEBUG) |
五、排查步骤:Redis 拒绝启动
- 先看日志最后一行:AOF 格式错误 / RDB 校验失败 / 权限或目录问题,三类原因分开治;
- 备份(!)后
redis-check-aof --fix或换 RDB 启动; - 启动后
INFO persistence+ Go 核对脚本验证完整度; - 写事故报告:丢数区间、根因(断电/fsync 策略/磁盘)、整改项;
- 整改项至少包含:
aof-load-truncated yes核查、UPS/云盘原子写、从库完整性巡检。
六、总结
- AOF 损坏拒启动是保护机制不是 bug:先备份、再 --fix;
- --fix 的代价是截断尾部,丢多少必须在修复前评估(RDB 对拍、从库对拍);
aof-load-truncated yes能自动救尾部残缺,中间损坏靠从库重建;- 抢救的收尾是完整度核对 + 数据库回补,只"启动成功"不算完。
下一篇预告:故障排查弧线最后一案------下一篇《maxclients 打满但 QPS 只有 500------连接数排查 5 步》,一个"幽灵连接大户"的抓捕现场。