RDB 文件损坏的检测与修复
RDB 是二进制快照,磁盘写满、进程被杀或存储介质故障都可能让文件损坏。损坏本身不可怕,可怕的是发现不了和处置顺序错误。本文给出检测手段、修复路径的优先级与一次完整的演练。
一、RDB 怎么坏的
常见成因按概率排序:
- 磁盘写满。 BGSAVE 写到一半失败,留下残缺文件;
stop-writes-on-bgsave-error yes会同时停止写入,业务立刻受影响。 - 写入中途进程被杀或掉电。 临时文件尚未 rename 成正式文件,正式文件还是上一代的完好版本。
- 拷贝中断。 用 scp/rsync 备份时网络中断,备份副本本身就是残缺的。
- 存储坏块。 底层介质损坏,文件字节被改写。
RDB 文件尾部有 CRC64 校验和(rdbchecksum yes 时写入),加载时校验失败会直接拒绝启动,这是损坏被发现的最后一道闸。
要理解"损坏位置"这个概念,需要知道 RDB 的物理结构。文件由四段组成:
- 魔数头:固定 9 字节,
REDIS加 4 位版本号,例如REDIS0011表示 RDB 版本 11(Redis 7.2); - AUX 元数据:键值对形式,记录
redis-ver、redis-bits、ctime、used-mem、aof-preamble等,用于诊断; - 数据库段:每个库以
SELECTDB操作码开头,跟着RESIZEDB(哈希表容量提示),然后是若干键值对,每个键值对由类型字节、过期时间(可选)、key、value 组成; - 结尾:
EOF操作码加 8 字节 CRC64 校验和,高版本还带一个MODULE_AUX段。
因为格式是顺序编码的,解析器只能"从头往后走"。一旦某个字节被改写或截断,解析会立刻失去对齐,报出的 offset 就是最早出现异常的位置——这就是为什么 redis-check-rdb 的进度输出能定位损坏范围。也正因为如此,损坏点之后的数据即便物理上完好,也无法可靠恢复,除非借助能按 opcode 重新对齐的第三方工具。
二、损坏的信号
redis-cli INFO persistence | grep rdb_last_bgsave_status
# rdb_last_bgsave_status:err
- 运行中实例:
rdb_last_bgsave_status变为 err,日志出现 "Failed saving" 或 "Short write"; - 启动阶段:日志报 "Bad file format" 或 CRC 校验失败,Redis 拒绝启动并退出。
注意:发现 status 为 err 时,第一动作是查磁盘空间与权限,而不是重启实例——如果磁盘上的 dump.rdb 已损坏而内存数据还在,重启等于主动引爆加载失败。
三、用 redis-check-rdb 体检
redis-check-rdb 只读不写,用于离线诊断(以下为节选格式):
redis-check-rdb /var/lib/redis/dump.rdb
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = '7.2.4'
[offset 54] AUX FIELD aof-preamble = '0'
[offset 66] Selecting DB ID 0
...
Size: 1073741824 bytes
RDB version: 11
Total keys: 5382104
CRC64 checksum is OK
关注三点:RDB version 与 Redis 版本对应(7.0 为 10、7.2 为 11);Total keys 与预期是否接近;CRC64 是否 OK。解析在中途报错,说明损坏位置就在附近;文件能完整解析只是校验和不对,数据大概率仍然可用。
四、修复路径的优先级
- 从存活的副本重建。 任一从库或原实例内存数据还在,直接挂一个新实例做全量同步,比修文件快得多也可靠得多,这是生产首选。
- 换用上一代备份。 备份链里取最近一份校验通过的快照,接受两次备份之间的数据损失。
- 跳过校验试探加载。 启动配置加
rdbchecksum no,Redis 会跳过 CRC 校验尝试加载,适用于"结构完整、校验和异常"的文件;加载后必须立即用 DBSIZE 与抽样比对验证数据可信度。 - 社区工具抢救。 redis-rdb-cli 等工具能按 opcode 解析到损坏点为止,把之前的 key 导出,适合无备份且损坏靠后的场景。
注意:永远在副本文件上操作,原始损坏文件先封存,它是事后归因的证据。
四条路径的时间成本差异很大,选之前先算清楚账:
| 路径 | 典型耗时 | 数据损失 | 适用前提 |
| --- | --- | --- | --- |
| 副本重建 | 分钟级到小时级,取决于数据量 | 无(副本是准实时同步) | 存在健康从库或原实例内存可用 |
| 换备份 | 分钟级,取决于备份介质 | 到上一份快照为止 | 有校验通过的备份链 |
| 跳过校验 | 分钟级 | 不确定,需抽样验证 | 仅校验和不匹配,结构完整 |
| 工具抢救 | 小时级,成功率视损坏位置 | 损坏点之后的全部数据 | 无副本、无备份 |
多数人第一反应是"把文件修好",但恢复目标从来不是文件,而是数据。只要还有一个健康副本,重建就是唯一应该考虑的方案——它绕过了所有格式层面的不确定性。
五、演练:模拟截断与恢复
# 1. 备份并注入损坏:截掉尾部 1KB
cp dump.rdb dump.rdb.bak
truncate -s -1024 dump.rdb
# 2. 体检:CRC 报错,keys 统计正常
redis-check-rdb dump.rdb
# 3. 跳过校验试探加载(隔离端口)
redis-server --port 6380 --dir /var/lib/redis --dbfilename dump.rdb --rdbchecksum no
redis-cli -p 6380 DBSIZE
演练目的有三:熟悉 redis-check-rdb 的输出;确认跳过校验加载的实际效果;把"发现—体检—恢复—验证"的动作写进 runbook,而不是等真故障时现场发明流程。
需要强调第 3 步只是"试探",不是结论。加载成功之后必须做三件事才算验证完成:DBSIZE 与损坏前的 key 数量比对,差值超过容忍范围就说明数据有丢失;用 SCAN 抽样若干 key 检查值是否合理;对关键业务 key 做一次 TTL 与 TYPE 检查,避免出现类型错乱的脏数据。跳过校验最危险的地方在于它把"明确的失败"变成了"静默的成功",如果不做验证,错误数据可能长期留在库里。
六、把检测做成例行任务
修复能力再强,也不如提前发现。三项低成本动作能把大部分损坏挡在故障之前:
- 备份后立即校验:备份脚本末尾追加
redis-check-rdb,失败则告警并保留上一份可用备份,不要覆盖。 - 落盘后就校验:
BGSAVE完成后读取rdb_last_bgsave_status,非 ok 立即通知,避免残缺文件被当成有效副本轮转出去。 - 定期抽样恢复:每月挑一个备份在隔离环境真实加载一次,验证"备份能恢复"这件事本身,而不是只看文件存在与大小。
这三项做下来,损坏要么在产生时就被拦截,要么在备份轮转时被发现,不会留到真正需要恢复的那一天。
常见坑 / 最佳实践
- 备份副本也要校验。 每次备份后跑一遍 redis-check-rdb 并记 md5,损坏的备份在需要时才发现是双倍打击。
- rdbchecksum no 不是修复。 它只是跳过校验,如果数据字节本身已被改写,加载成功也可能带着错误数据,必须做业务侧抽样验证。
- 磁盘告警阈值前置。 磁盘使用率超过 80% 告警,给 BGSAVE 的临时文件留足空间,比任何修复手段都省事。
- 修复顺序先副本后文件。 有存活数据源就重建,别在损坏文件上耗费半天,文件修复是最后的手段。
小结
- 损坏的三大来源是磁盘满、进程中断与拷贝中断,CRC64 校验是最后的检测闸门。
rdb_last_bgsave_status:err是运行期信号,处置动作是查磁盘而非重启。- redis-check-rdb 能给出解析进度、key 统计与校验结果,是所有处置路径的第一步。
- 修复优先级:副本重建 > 换备份 > 跳过校验试探 > 社区工具抢救,且永远在副本文件上操作。
- 备份校验与磁盘容量告警是把故障概率压到最低的两项基础工作。
- 点赞
- 收藏
- 关注作者
评论(0)