RDB 文件损坏的检测与修复

举报
yd_227606267 发表于 2026/11/09 07:31:08 2026/11/09
【摘要】 从告警信号到 redis-check-rdb 与副本重建的处置路径

RDB 是二进制快照,磁盘写满、进程被杀或存储介质故障都可能让文件损坏。损坏本身不可怕,可怕的是发现不了和处置顺序错误。本文给出检测手段、修复路径的优先级与一次完整的演练。

一、RDB 怎么坏的

常见成因按概率排序:

  • 磁盘写满。 BGSAVE 写到一半失败,留下残缺文件;stop-writes-on-bgsave-error yes 会同时停止写入,业务立刻受影响。
  • 写入中途进程被杀或掉电。 临时文件尚未 rename 成正式文件,正式文件还是上一代的完好版本。
  • 拷贝中断。 用 scp/rsync 备份时网络中断,备份副本本身就是残缺的。
  • 存储坏块。 底层介质损坏,文件字节被改写。

RDB 文件尾部有 CRC64 校验和(rdbchecksum yes 时写入),加载时校验失败会直接拒绝启动,这是损坏被发现的最后一道闸。

要理解"损坏位置"这个概念,需要知道 RDB 的物理结构。文件由四段组成:

  1. 魔数头:固定 9 字节,REDIS 加 4 位版本号,例如 REDIS0011 表示 RDB 版本 11(Redis 7.2);
  2. AUX 元数据:键值对形式,记录 redis-ver、redis-bits、ctime、used-mem、aof-preamble 等,用于诊断;
  3. 数据库段:每个库以 SELECTDB 操作码开头,跟着 RESIZEDB(哈希表容量提示),然后是若干键值对,每个键值对由类型字节、过期时间(可选)、key、value 组成;
  4. 结尾: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。解析在中途报错,说明损坏位置就在附近;文件能完整解析只是校验和不对,数据大概率仍然可用。

四、修复路径的优先级

  1. 从存活的副本重建。 任一从库或原实例内存数据还在,直接挂一个新实例做全量同步,比修文件快得多也可靠得多,这是生产首选。
  2. 换用上一代备份。 备份链里取最近一份校验通过的快照,接受两次备份之间的数据损失。
  3. 跳过校验试探加载。 启动配置加 rdbchecksum no,Redis 会跳过 CRC 校验尝试加载,适用于"结构完整、校验和异常"的文件;加载后必须立即用 DBSIZE 与抽样比对验证数据可信度。
  4. 社区工具抢救。 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 立即通知,避免残缺文件被当成有效副本轮转出去。
  • 定期抽样恢复:每月挑一个备份在隔离环境真实加载一次,验证"备份能恢复"这件事本身,而不是只看文件存在与大小。

这三项做下来,损坏要么在产生时就被拦截,要么在备份轮转时被发现,不会留到真正需要恢复的那一天。

常见坑 / 最佳实践

  1. 备份副本也要校验。 每次备份后跑一遍 redis-check-rdb 并记 md5,损坏的备份在需要时才发现是双倍打击。
  2. rdbchecksum no 不是修复。 它只是跳过校验,如果数据字节本身已被改写,加载成功也可能带着错误数据,必须做业务侧抽样验证。
  3. 磁盘告警阈值前置。 磁盘使用率超过 80% 告警,给 BGSAVE 的临时文件留足空间,比任何修复手段都省事。
  4. 修复顺序先副本后文件。 有存活数据源就重建,别在损坏文件上耗费半天,文件修复是最后的手段。

小结

  • 损坏的三大来源是磁盘满、进程中断与拷贝中断,CRC64 校验是最后的检测闸门。
  • rdb_last_bgsave_status:err 是运行期信号,处置动作是查磁盘而非重启。
  • redis-check-rdb 能给出解析进度、key 统计与校验结果,是所有处置路径的第一步。
  • 修复优先级:副本重建 > 换备份 > 跳过校验试探 > 社区工具抢救,且永远在副本文件上操作。
  • 备份校验与磁盘容量告警是把故障概率压到最低的两项基础工作。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。