3. RDB快照原理深入
RDB(Redis Database)是 Redis 最经典的持久化方式,它把某一时刻内存中的键值数据“定格”成一个紧凑的二进制文件。理解 RDB,关键不是记住“它会存盘”,而是搞清楚四件事:什么时候触发、fork 与写时复制如何让快照与主线程并存、文件里到底存了什么、生成过程对性能有什么影响。这四点串起来,才能解释为什么 RDB 恢复快、文件小,却可能丢数据、也可能在 fork 瞬间造成延迟抖动。
一、触发方式:手动与自动两条路径
RDB 的触发分为显式命令和自动条件两类。显式命令有三个:SAVE 在主线程同步执行,会阻塞所有客户端,生产环境基本不用;BGSAVE fork 出子进程后台生成,是主流方式;BGREWRITEAOF 虽然属于 AOF 重写,但在重写期间若配置允许,也可能触发一次 RDB 用于 AOF 重写的基础数据。
自动触发由配置项 save 控制,格式为 save <seconds> <changes>,表示“在 seconds 秒内至少有 changes 个键发生变化”就触发 BGSAVE。例如:
save 900 1
save 300 10
save 60 10000
这三条是 Redis 默认配置,含义是:900 秒内至少 1 次修改、300 秒内至少 10 次修改、60 秒内至少 10000 次修改,任意一条满足即触发。此外,执行 SHUTDOWN 时若未开启 AOF,Redis 会执行一次 SAVE 保证数据落盘;主从复制中,从节点首次连接主节点时,主节点也会触发 BGSAVE 生成 RDB 传给从节点。
二、fork 与写时复制:快照与主线程如何共存
BGSAVE 的核心是 fork()。fork 会创建一个子进程,子进程拥有与父进程几乎相同的地址空间副本。但 Linux 的 fork 采用写时复制(Copy-On-Write,COW):fork 瞬间并不真正复制物理内存,父子进程共享同一份物理页,只有任意一方写入某页时,内核才为该页复制一份新副本。
这带来两个重要结果。第一,fork 本身很快,因为它只复制页表,不复制数据页;但页表越大,fork 耗时越长,这是大内存实例 fork 卡顿的根源。第二,子进程看到的是 fork 那一刻的内存快照,因为父进程后续的写操作会触发 COW,父进程改的是新页,子进程仍指向旧页,所以快照数据不会被污染。
但 COW 不是免费的。如果 fork 后主线程写入非常频繁,大量页被复制,内存占用会显著上升,极端情况下可能接近翻倍。因此 RDB 期间要关注 rdb_bgsave_in_progress、rdb_last_cow_size 等指标,并避免在高峰期做大批量写入。
三、快照文件结构:紧凑的二进制布局
RDB 文件是一个二进制流,整体结构可以概括为“头部 + 数据区 + 尾部”。头部以魔数 REDIS 开头,后跟 4 位版本号,例如 REDIS0011 表示 RDB 版本 11。数据区由若干“操作码 + 数据”组成,常见操作码包括:0xFA 辅助字段(如 redis-ver、redis-bits)、0xFE 选择数据库、0xFB 哈希表大小、0xFD 过期时间、0xFF 文件结束。尾部是 8 字节 CRC64 校验和,用于检测文件损坏。
数据区中每个键值对按“类型 + 键 + 值”编码。类型字节决定值的编码方式,例如字符串、列表、集合、哈希、有序集合各有不同编码,Redis 还会根据数据特征选择紧凑编码(如 ziplist、intset、listpack)以节省空间。过期时间以毫秒时间戳存储,恢复时若已过期则跳过。这种结构使 RDB 文件非常紧凑,加载时只需顺序解析,因此恢复速度远快于 AOF 重放。
四、生成流程与性能影响
BGSAVE 的完整流程是:主线程检查是否已有子进程在跑,若无则调用 fork;子进程遍历内存中的数据库,把键值按 RDB 格式写入临时文件;写完后用 rename 原子替换旧文件;最后子进程退出,主线程通过信号感知完成,更新 rdb_last_save_time 等状态。整个过程主线程只在 fork 和信号处理时短暂参与,大部分时间不阻塞。
性能影响主要有三处。第一,fork 延迟:与页表大小正相关,内存越大越明显,几十 GB 实例 fork 可能达到数百毫秒。第二,COW 内存膨胀:写入越频繁,复制的页越多,内存峰值越高,可能触发 OOM。第三,磁盘 I/O 竞争:子进程写盘会占用磁盘带宽,若与 AOF 重写或主线程日志写入叠加,可能拖慢整体响应。
save 条件设置得比业务写入节奏更宽松,避免频繁 BGSAVE;监控 latest_fork_usec 和 rdb_last_cow_size;在从节点或低峰期执行手动 BGSAVE;若实例内存很大,可考虑关闭自动 RDB,改用主从 + AOF 组合。理解 RDB 的关键,是把“快照”看成一次基于 COW 的时间点冻结:fork 提供视图隔离,COW 提供低成本复制,二进制结构提供紧凑存储,而性能代价集中在 fork 瞬间和写入密集时的内存复制。掌握这四者的关系,就能在恢复速度、数据安全与运行稳定性之间做出合理取舍。