当Redis的RDB文件损坏时,第一时间不是去恐慌,而是立刻切断所有写入流量。如果你用的是主从架构,立即执行"slaveof no one"将从库提升为主库,或者将流量切换到备节点。如果是单机且没有从库,立刻在配置文件中设置"stop-writes-on-bgsave-error yes"(默认已是yes),防止基于错误数据继续写入导致AOF也被污染。接下来,马上复制一份损坏的RDB文件到安全位置,所有操作都在副本上进行,保留原始现场。
使用redis-check-rdb工具进行诊断和修复Redis自带的redis-check-rdb工具是第一道防线。进入Redis安装目录或者直接使用命令行,执行:
redis-check-rdb /path/to/dump.rdb
这个命令会扫描整个RDB文件,报告发现的错误类型和位置。常见错误包括:文件截断、校验和错误、键值对不完整等。如果工具报告的是文件尾部截断,数据丢失量通常较小,它会自动尝试修复并生成一个可用的RDB文件。如果是中间部分损坏,工具会指出第一个错误发生的位置,这能帮你估算出哪些数据可能丢失了。注意,redis-check-rdb修复后的文件会直接覆盖原文件,所以务必提前备份。
如果redis-check-rdb直接报错退出,无法生成有效文件,你需要使用"--fix"参数进行强制修复尝试:
redis-check-rdb --fix /path/to/dump.rdb
这个操作会丢弃所有无法解析的部分,从文件头开始逐条恢复可识别的键值对。修复完成后,用"redis-check-rdb"再次检查生成的文件,确认没有新的错误。修复后的文件通常比原文件小,因为损坏的数据段被直接跳过了。
手动解析RDB文件提取剩余数据当工具修复失败,或者你想最大限度地抢救数据时,需要直接解析RDB文件的二进制结构。RDB文件有明确的格式规范:文件头以"REDIS"魔数开头,后面跟着版本号,然后是数据库选择器、键值对数据,最后以校验和结尾。你可以使用Python或者Go编写简单的解析脚本。
这里给出一个Python解析框架,基于rdb-parser库(需要先安装:pip install rdbtools python-lzf):
from rdbtools import RdbParser, RdbCallback
from rdbtools.encodehelpers import bytes_to_unicode
class EmergencyCallback(RdbCallback):
def __init__(self):
self.recovered_keys = 0
self.failed_keys = 0
def set(self, key, value, expiry, info):
try:
# 在这里处理每一个成功解析的键值对
print(f"Recovered: {bytes_to_unicode(key)}")
self.recovered_keys += 1
except Exception as e:
self.failed_keys += 1
print(f"Failed to parse key at position {info}")
def start_database(self, db_number):
print(f"Processing database {db_number}")
callback = EmergencyCallback()
parser = RdbParser(callback, filters={})
try:
parser.parse('/path/to/corrupted_dump.rdb')
except Exception as e:
print(f"Parsing stopped at error: {e}")
finally:
print(f"Total recovered: {callback.recovered_keys}, Failed: {callback.failed_keys}")
这个脚本会逐个解析键值对,遇到损坏部分时抛出异常并停止。你可以捕获异常后,手动调整文件偏移量跳过损坏区域继续解析。实际操作中,你需要用十六进制编辑器打开RDB文件,找到异常发生位置,跳过若干字节后重新尝试解析。这需要你对RDB编码格式有一定了解:字符串类型有长度前缀编码,列表、集合等有元素计数,跳过损坏的键时要注意对齐到下一个合法的操作码。
针对特定数据结构的深度恢复如果你的Redis中存储了大量hash、list、set等复杂结构,这些数据在RDB中是以特定编码序列化的。当工具无法恢复时,你可以针对性地提取。例如,hash类型在RDB中先存储hash表大小,然后依次存储field-value对。如果某个hash内部部分field损坏,你可以修改文件偏移量跳过该field,恢复同一个hash中的其他field。
对于zset类型,它有两种内部编码:ziplist和skiplist。ziplist编码的数据连续存储,损坏后很难部分恢复;而skiplist编码的zset,member和score分开存储,你可以通过分析文件结构,提取出未损坏的member-score对。使用redis-check-rdb时加上"--dump-keys"参数可以列出文件中包含的所有键名,这能帮你判断哪些键可能完整幸存:
redis-check-rdb --dump-keys /path/to/dump.rdb > keys_list.txt
有了键名列表,你可以对比业务重要性,优先抢救核心数据。如果某些键的业务价值极高,即使只能恢复部分字段也值得手动解析。
利用AOF文件进行互补恢复如果你的Redis同时开启了AOF持久化,即使RDB完全损坏,AOF文件往往保存着更完整的数据。检查AOF文件是否完好:
redis-check-aof --fix /path/to/appendonly.aof
这个命令会修复AOF文件尾部不完整的事务。修复后,你可以直接使用AOF文件启动Redis:
redis-server --appendonly yes --appendfilename appendonly.aof
如果RDB和AOF都部分损坏,可以尝试合并两者数据。先用修复后的RDB启动一个临时Redis实例,然后使用"redis-cli --pipe"命令将修复后的AOF中增量命令导入。具体做法是提取AOF文件中RDB快照时间点之后的所有写命令,通过管道批量发送到临时实例。注意过滤掉已存在于RDB中的重复命令,避免数据冲突。
操作系统层面的文件恢复手段如果RDB文件是因为磁盘故障或意外断电导致损坏,在文件系统层面可能还有挽回余地。对于ext4文件系统,使用"debugfs"工具可以查看文件的历史块。如果是xfs文件系统,"xfs_repair"可以修复文件系统元数据。但要注意,这些操作必须在卸载分区或以只读方式挂载的情况下进行,任何额外的写入都可能覆盖掉可恢复的数据块。
更底层的手段是直接读取磁盘原始块设备,搜索RDB文件的魔数"REDIS"(十六进制:52 45 44 49 53),定位文件碎片。使用"dd"命令从指定偏移量提取数据:
dd if=/dev/sda1 bs=512 skip=OFFSET count=SIZE of=recovered_chunk.rdb
这需要你事先知道RDB文件的大致存储位置,可以通过文件的inode信息反查磁盘块映射。这种方法技术门槛高,成功率取决于文件碎片化程度和损坏后是否有新数据覆盖,但在极端情况下是最后的希望。
预防措施和监控体系抢救数据是亡羊补牢,建立完善的预防体系才是根本。首先,开启RDB和AOF混合持久化(Redis 4.0+),在redis.conf中设置:
aof-use-rdb-preamble yes
这样AOF文件前半部分是RDB格式的快照,后半部分是增量命令,既保证了恢复速度,又提高了数据安全性。其次,配置多级备份策略:本地保留最近7天的RDB文件,使用脚本每天将备份同步到远程存储或对象存储服务。备份脚本需要验证文件完整性:
#!/bin/bash
redis-cli BGSAVE
sleep 10
if redis-check-rdb /var/lib/redis/dump.rdb | grep -q "Checksum OK"; then
cp /var/lib/redis/dump.rdb /backup/redis/dump_$(date +%Y%m%d).rdb
else
echo "RDB verification failed!" | mail -s "Redis Backup Alert" admin@example.com
fi
监控方面,除了常规的Redis存活监控,还要监控"rdb_last_bgsave_status"和"rdb_last_save_time"指标。如果bgsave状态异常,立即告警。设置"redis-cli INFO persistence"的定时采集,跟踪RDB文件大小变化趋势,文件大小异常减小可能意味着数据丢失。
最后,定期进行恢复演练。每季度从备份文件中随机抽取一个RDB文件,在隔离环境中完整恢复并校验关键业务数据是否齐全。只有经过实际恢复验证的备份才是有效的备份。
