土法炼钢兴趣小组的算法知识备份

【Redis / 缓存内核】RDB:fork COW · BGSAVE · 快照格式

文章导航

分类入口
databasestorage
标签入口
#redis#rdb#bgsave#fork#cow#persistence#snapshot#7.4

目录

第 8 篇 讨论内存触顶时的 key 驱逐;本篇进入持久化第一条路径:RDB 快照。RDB 把某一时刻的内存数据集序列化成紧凑二进制文件,恢复时直接 load 进结构,比重放 AOF 命令快,但两次快照之间的写入默认会丢——这是快照语义,不是实现 bug。

本文锚定 Redis OSS 7.4.x rdb.c / rdb.hRDB_VERSION 12)。不粘贴本机 BGSAVE 延迟或 COW 体积数字;fork 代价与数据集、写压力强相关,需在生产相似环境实测。

本文是「Redis / 缓存内核」系列第 9 篇(共 16 篇)。→ 系列目录

版本锚定:Redis OSS 7.4 / 8.x;RDB 格式版本 12REDIS0012 魔数)。


一、问题:快照是时间点副本,不是连续日志

数据库持久化两条经典路线在 Redis 里并存:

路线 Redis 实现 恢复速度 丢失窗口
Snapshot RDB 快(顺序读二进制) 上次成功快照 → 崩溃
Command log AOF 慢(重放 RESP) 取决于 appendfsync(第 10 篇)

RDB 对应文献里的 checkpoint / snapshot recovery:在 \(t_0\) 冻结一致视图,崩溃后回到 \(t_0\)。与 LSM 的 SST 快照不同,Redis RDB 在单进程内存状态上通过 fork 获得子进程只读视图,父进程继续服务写请求——依赖 OS copy-on-write(COW):子进程读页时与父共享物理页,父修改时复制页。

工程痛点:大内存 + 写密集 → COW 破裂页多 → 父进程 RSS 近乎翻倍、fork 本身阻塞主线程数毫秒到数秒——第 15 篇 会收束排障;本篇钉机制与格式。


二、SAVE 与 BGSAVE:同步 vs 后台

2.1 SAVE:阻塞式

SAVE 命令在主线程调用 rdbSave()rdbSaveInternal(),直接写磁盘直到完成。期间事件循环无法处理其他命令——生产禁用,仅适合维护窗口或空实例验证。

2.2 BGSAVE:fork 子进程

BGSAVErdbSaveBackground()

int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
    if (hasActiveChildProcess()) return C_ERR;
    if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
        /* Child */
        retval = rdbSave(req, filename, rsi, rdbflags);
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent 继续服务 */
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        return C_OK;
    }
}

约束:

复制全量同步也可通过 rdbSaveToSlavesSocketsdiskless RDB(socket 直传),格式与文件 RDB 相同,省略本地临时文件。

sequenceDiagram
  participant M as Parent main thread
  participant C as Child redis-rdb-bgsave
  participant D as Disk / Socket
  M->>C: redisFork CHILD_TYPE_RDB
  Note over M: continues serving writes<br/>COW on modified pages
  C->>D: rdbSaveRio walk keys
  C-->>M: exit status
  M->>M: rename temp.rdb, update lastsave

2.3 COW 语义边界

fork 后子进程遍历的是逻辑上一致的内存图;父进程新写入的 key 不会进入本次 RDB。父进程修改已有 key 会触发 COW 复制底层页——内存压力来自「变更页」而非「子进程读」本身。大 key(巨型 Hash/Stream)的单页或连续页变更成本高,与 第 7 篇 lazyfree 删除大对象叠加时,RSS 曲线常呈锯齿。

子进程在 7.4 可对已序列化对象调用 dismissObject() 提示内核回收页(madvise 类路径),减轻双份占用——是否生效取决于 OS 与分配器。


三、RDB 格式锚点

rdbSaveRio() 是写路径核心;读路径对称为 rdbLoadRio()

3.1 文件头

snprintf(magic, sizeof(magic), "REDIS%04d", RDB_VERSION);
/* Redis 7.4: RDB_VERSION = 12 → "REDIS0012" */

随后可选 AUX 字段RDB_OPCODE_AUX):redis-verredis-bitsctimeused-mem 等元数据,供运维与兼容性检查。

3.2 逻辑结构(opcode 驱动)

Opcode / 段 含义
RDB_OPCODE_SELECTDB 切换 DB 编号
RDB_OPCODE_RESIZEDB 预告 hash 大小(加速 rehash)
RDB_OPCODE_SLOT_INFO Cluster slot 映射(集群)
RDB_OPCODE_EXPIRETIME_MS 紧随 key 的毫秒过期
RDB_OPCODE_IDLE LRU idle(可选)
RDB_OPCODE_FREQ LFU 频率(可选)
RDB_OPCODE_FUNCTION2 Functions(7.x)
RDB_OPCODE_MODULE_AUX 模块辅助数据
类型字节 + key + value OBJ_* 编码(string/list/hash/zset/stream…)
RDB_OPCODE_EOF 结束
CRC64 校验和(可选 rdbchecksum

值编码与 第 3–6 篇type/encoding 一一对应:如 ziplist/listpack 紧凑形式、skiplist+dict 的 ZSet 等。读入时可能触发 encoding 升级(与在线写入路径相同规则)。

3.3 单 key 写序

rdbSaveKeyValuePair() 顺序:可选过期 opcode → rdbSaveObjectType → key → rdbSaveObject(按类型分支)。过期时间用毫秒(RDB_OPCODE_EXPIRETIME_MS);旧版秒级 EXPIRETIME 仍可读。


四、触发方式与配置

机制 配置 / 命令
手动 SAVEBGSAVE
定时 save <sec> <changes>,在 serverCron 检查 dirty 计数
关闭前 shutdown save(默认)
复制 全量同步前子进程或 diskless 生成 RDB

stop-writes-on-bgsave-error yes 时,上次 BGSAVE 失败则拒绝写——避免在无持久化保障下继续累积无法快照的数据。

无盘实例appendonly yes 且禁用 RDB):官方支持纯 AOF;RDB 仍可作为复制与混合 AOF base 的载体(第 11 篇)。


五、学术谱系与争论

谱系:System R 影子页 / fuzzy checkpoint → Redis antirez 时代 RDB 二进制格式 → 7.x 多模块、Function、Cluster slot 扩展 opcode。

争论

  1. RDB vs 仅 AOF:RDB 恢复更快、文件更紧凑;丢失窗口大。金融级「每条提交可恢复」不应单靠定时 RDB。
  2. fork vs 无 fork 快照:fork 模型简单且与单线程兼容,但内存倍化风险使大实例运维谈 BGSAVE 色变;diskless 缓解磁盘 IO,不消除 COW。
  3. checksumrdbchecksum 防静默损坏;与 数据完整性 端到端校验互补,不覆盖传输后内存位翻转。

开放问题:TB 级内存实例上 fork 延迟与 COW 峰值仍缺通用公式;Linux MADV_DONTNEED / dismiss 与 jemalloc 交互在不同内核版本上行为不一,需结合 INFO persistence/proc 实测。


六、常见误解

6.1 「BGSAVE 不阻塞主线程」

fork 瞬间与 COW 压力仍影响延迟;只是不阻塞在写完整文件上。LATENCY DOCTOR 可能出现 fork 事件。

6.2 「RDB 包含 fork 之后的写入」

不包含。RDB 是子进程开始遍历时的切片;之后写入靠 AOF 或下一次快照。

6.3 「SAVE 比 BGSAVE 更安全」

两者快照语义相同;SAVE 只是阻塞更久,不增加持久性级别。

6.4 「RDB 文件是命令日志」

不对。RDB 是序列化对象;AOF 才是 RESP 命令文本(或混合 preamble 的 RDB 段)。


七、小结

三句话小结

  1. BGSAVE 通过 fork 子进程写 rdbSaveRio,父进程继续写,一致性停在 fork 时刻。
  2. COW 使父进程修改页可能双倍占内存;大 key 与写密集是主要风险。
  3. 格式REDIS0012 开头,opcode 流编码多 DB、过期、LRU/LFU 与各 encoding 对象。

上一篇maxmemory 淘汰

下一篇AOF 命令日志


参考资料

  1. Redis OSS 7.4 源码:src/rdb.crdbSaveBackgroundrdbSaveRiordbSaveKeyValuePair)、src/rdb.hRDB_VERSIONRDB_OPCODE_*)。
  2. Redis Documentation v7.x, PersistenceRDBReplication(diskless)。
  3. 数据完整性:fsync 与端到端校验(静默损坏语境)。
  4. 系列 index第 10 篇 AOF第 11 篇混合持久化

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .