2. MongoDB存储引擎与持久化机制
要理解 MongoDB 的备份恢复为什么能成立、恢复到什么时间点、恢复后副本集如何重新同步,必须先看清 WiredTiger 存储引擎在磁盘上留下了哪些“证据”。MongoDB 从 3.2 起默认使用 WiredTiger 作为存储引擎,它同时维护三类关键持久化对象:数据文件(collection-*.wt / index-*.wt)、journal 日志(WiredTigerLog),以及副本集层面的 oplog(local.oplog.rs)。三者职责不同、写入时机不同,共同决定了备份的边界与恢复的精度。
一、WiredTiger 的写入路径:从内存到磁盘
WiredTiger 采用“先写日志、后写数据文件”的持久化策略。一次写操作进入引擎后,大致经历如下路径:
- 写入操作先进入 WT 内存中的页缓存(cache),并记录到 journal 缓冲区;
- 默认情况下(
writeConcern: {j: true}或副本集多数确认),journal 缓冲区按约 100ms 的周期刷盘,形成 journal 文件; - 内存中的脏页由 eviction(淘汰)线程在达到阈值时刷入数据文件;
- 每隔 60 秒(或 journal 数据达到 2GB)触发一次 checkpoint,把当前一致的内存视图落成数据文件中的一致点,并更新元数据。
因此,journal 保证“崩溃可恢复”,checkpoint 保证“数据文件本身一致”。二者缺一不可:只有 checkpoint 没有 journal,崩溃后会丢失最近一次 checkpoint 之后的所有写入;只有 journal 没有 checkpoint,恢复时需要重放大量日志,启动极慢。
二、checkpoint、journal 与数据文件的关系
可以把 WiredTiger 的磁盘状态理解为“数据文件 = 若干历史 checkpoint 的叠加”。每个 checkpoint 在数据文件中标记一个一致点,journal 则记录该 checkpoint 之后的所有变更。恢复时,MongoDB 从最近一次有效 checkpoint 开始,重放 journal 中尚未落盘的记录,使数据文件回到崩溃前的一致状态。
需要特别注意的是:journal 只服务于单节点崩溃恢复,不参与跨节点复制。副本集节点之间的数据同步依赖的是 oplog,而不是 journal。这是备份恢复设计中极易混淆的一点。
三、oplog:副本集层面的“操作流水”
oplog 是 local 库中的固定大小 capped collection(默认约为磁盘空间的 5%,可在 1GB~50GB 之间调整)。主节点把每一次数据变更以幂等操作的形式写入 oplog,从节点拉取并重放这些操作,从而保持数据一致。oplog 记录的是逻辑操作(如 i 插入、u 更新、d 删除、c 命令),而不是物理页变更。
oplog 有两个关键属性:幂等性(同一条 oplog 重放多次结果一致)和时间窗口(由 oplog 大小和写入速率共同决定)。如果从节点落后超过 oplog 窗口,就会进入 RECOVERING 状态,需要全量重新同步。
四、三者的协同关系
下图展示了 WiredTiger 内部持久化对象与副本集 oplog 之间的层次与数据流关系:
五、对备份恢复的直接影响
理解了上述关系,就能解释几个备份恢复中的关键结论:
- 物理备份(如文件系统快照)必须与 checkpoint 对齐。直接复制数据文件时,如果复制过程中发生了写入,得到的文件可能处于不一致状态。正确做法是:先执行
db.fsyncLock()锁定写入,再复制数据文件,最后db.fsyncUnlock()解锁。这样复制到的就是某个 checkpoint 的一致视图。 - 逻辑备份(mongodump)不依赖 checkpoint。它通过读取集合数据生成 BSON 文件,但默认不包含 oplog 增量,因此只能恢复到备份开始时刻的状态。
- 时间点恢复(PITR)依赖 oplog。要恢复到“昨天 14:30”这样的精确时间点,必须同时具备:一份基础全量备份 + 从该备份时间点开始的完整 oplog 片段。恢复时先还原全量备份,再用
mongorestore --oplogReplay重放 oplog 到目标时间戳。 - oplog 窗口决定增量备份的可行性。如果两次备份间隔超过 oplog 窗口,中间的操作已经滚出 oplog,就无法做增量恢复,只能重新全量备份。
db.getReplicationInfo() 查看 oplog 的 timeDiff(窗口秒数)和 tFirst/tLast 时间戳。如果窗口只有几小时,而备份周期是每天一次,就必须扩大 oplog 或缩短备份间隔,否则 PITR 能力形同虚设。
六、一个可参考的检查清单
在搭建备份恢复体系前,建议逐项确认以下内容,确保底层依据清晰:
| 检查项 | 查看方式 | 判断标准 |
|---|---|---|
| 存储引擎类型 | db.serverStatus().storageEngine | 应为 wiredTiger |
| journal 是否开启 | db.serverStatus().wiredTiger.log | 生产环境必须开启 |
| checkpoint 间隔 | 默认 60s,可通过 --wiredTigerCheckpointDelaySecs 调整 | 与备份窗口匹配 |
| oplog 大小与窗口 | db.getReplicationInfo() | 窗口 ≥ 备份周期 + 恢复缓冲 |
| oplog 起始时间 | db.oplog.rs.find().sort({$natural:1}).limit(1) | 早于最近一次全量备份 |
| 数据文件路径 | db.serverStatus().wiredTiger 或配置文件 | 快照/复制路径正确 |
把这三类持久化对象的关系理清之后,后续无论是选择物理快照、mongodump 还是 oplog 增量,都能明确知道“备份到了什么”“能恢复到哪一刻”“恢复后副本集如何重新对齐”。这正是备份恢复体系能够被信任的底层依据。