一 研究背景需求
目前作者所在公司的 MongoDB 数据库是每天凌晨做一次全库完整备份, 但数据库出现故障时, 只能保证恢复到全备时间点, 比如, 00:30 做的完整备份, 而出现故障是下午 18:00, 那么现有的备份机制只可以恢复到 00:30, 即丢失 00:30 - 18:00 的操作数据.
此外, 我们现在的副本集没有 delay 节点, 当出现误操作或需要恢复到指定时间点操作时, 目前灾备机制也不支持此操作. 上线这种备份方案, 心里总是惶惶的.
并且细究 mongodump 机制原理, 此命令在运行过程中并不会把数据库锁死(或建立快照, 以保证整个库冻结在一个固定的时间点), 实现数据库完整性, 而是细化到集合级别, 如此, 会导致数据完整性问题. 例如, 集合 A 中存放了订单概要信息, 集合 B 中存放了订单的所有明细, 那么只有一个订单有完整的明细时才是正确的状态. 那么备份时, 如果备份集合 A 处于时间点 x, 而备份集合 B 处于 x 之后的一个时间点 y 时, 可以想象 A 和 B 中的数据极有可能不对应而失去意义(部分订单有订单明细而没有订单概要信息).
二 原理分析
关系型数据库, 例如 MySQL ,SQL Server 都有事务日志(或 bin log), 会将数据库的 DML, DDL,DCL 等操作记录在事务文件中, 可以通过日志备份搭建还原体系. MongoDB 没有此类机制和数据文件, 难以实现.
但 MongoDB 副本集 有通过 oplog(主要记录在 local 数据库 oplog.rs 集合中) 实现节点间的同步, 此集合记录了数据库的 OP 操作, 记录的是整个 mongod 实例一段时间内的所有变更 (插入 / 更新 / 删除) 操作.
是否可以考虑通过 oplog.rs 集合的备份还原来解决以上问题(数据完整性; 不能还原到指定时间点; 时效性差.).
值得注意的是, oplog 为 replica set 或者 master/slave 模式专用, standalone 模式运行 MongoDB 并不推荐.
查看 MongoDB 备份命令 Mongodump, 其中有一个相关参数 oplog.
Mongodump 中 --oplog 参数
参数 | 参数说明 |
--oplog | Use oplog for taking a point-in-time snapshot |
该参数的主要作用是我们在导出库集合数据的同时生成一个 oplog.bson 文件, 里面存放了开始进行 dump 到 dump 结束之间所有的 op log 操作.
注意:--oplog 选项只对全库导出有效.
相应的 Mongorestore 中与 Oplog 相关的参数
参数 | 参数说明 |
oplogReplay | replay oplog for point-in-time restore |
oplogLimit | only include oplog entries before the provided Timestamp |
oplogFile | oplog file to use for replay of oplog |
三 验证测试
3.1 场景 1 备份还原后, 如何保证数据一致性, 完整性
在 Insert 数据过程中, 备份数据库, 为说明问题, 数据插入跨越整个备份过程.
3.1.1 在不使用 --oplog 参数下备份还原
Step 1 向数据库 ygtest041602 插入数据, 源库没有集合 order0531,orderdetial
插入语句:
- for(var i = 0; i < 10000; i++)
- {
- db.order0531.insert({
- a: i
- });
- };
- for(i=0;i<300000;i++)
- {
- db.orderdetial.insert({
- "id":i,"name":"shenzheng","addr":"龙岗","date":new Date()
- });
- };
step 2 在上述命令执行期间 执行 mongodump 备份
./mongodump -h 172.177.XXX.XXX --port 端口 --authenticationDatabase admin -u 用户名 -p 密码 --gzip -o /data/mongodb_back/mongotestdump
Step 3 还原上述备份文件
./mongorestore -h 172.177.XXX.XXX --port 端口 --authenticationDatabase admin -u 用户名 -p 密码 --gzip /data/mongodb_back/mongotestdump
Step 4 检查源库 Insert 语句, 执行完毕(一定是 mongodump 完毕, 再 insert 结束; 但不强调, mongorestore 与 insert 的时间关系)
Step 5 待语句执行完毕后, 比较 源库和 还原库 的数据.
源库
还原库
我们看到集合 orderdetial 在两个数据库不一致, 有差异. 即这种备份还原 无法保证数据一致性.
3.1.2 增加 参数 --oplog 参数下备份还原
Step 1 删除 上次测试中遗留的集合 和 备份文件
Step 2 启动 insert 命令
- for(var i = 0; i < 10000; i++)
- {
- db.order0531.insert({
- a: i
- });
- };
- for(i=0;i<150000;i++)
- {
- db.orderdetial.insert({
- "id":i,"name":"shenzheng","addr":"龙岗","date":new Date()
- });
- };
Step 3 执行备份命令
./mongodump -h 172.177.XXX.XXX --port 端口 --oplog --authenticationDatabase admin -u 用户名 -p 密码 --gzip -o /data/mongodb_back/mongotestdump
从打印结果来看, 有对 oplog 命令的输出, 查看备份文件 多了一个 oplog.bson 文件
Step 4 执行还原命令, 增加参数 --oplogReplay
./mongorestore -h 172.177.XXX.XXX --port 端口 --oplogReplay --authenticationDatabase admin -u 用户名 -p 密码 --gzip /data/mongodb_back/mongotestdump
从执行情况来看, 此类还原命令有还原 oplog
Step 5 比对数据
源库
还原库
本场景测试结论:
虽然结果数据仍不一致, 但从后者的备份还原过程可以看出, 有对 oplog 做单独的处理.
数据可以保证以 oplog 结尾为准, 实现了多集合 (表) 的时间一致性(MongoDB 本身特性, 不保证体现数据库的事务一致性).
3.2 场景二 还原到指定时间点
使用场景 3.1.2 的备份文件, 测试还原到备份过程中的某个时刻. 从上次备份过程可知, 开始时间为 2018-05-31T11:13:46.501+0800, 结束时间为 2018-05-31T11:14:12.961+0800
此轮测试还原点选择在此时间段内. 时间还原点的选择, 假如我们还原到 orderdetial 集合 "_id" : ObjectId("5b0f6876c52291864d3485b9")的插入时的时间点.
查看此时间点对应的时间和操作序列, 在源库 local 中的 oplog.rs 集合查询.
- (oplog.rs 集合的数据意义, 我们在其他章节介绍, 在此不做赘述)
- "ts" : Timestamp(1527736438, 858)
此文档对应的 "id" : 9719.0, 插入时间为 2018-05-31T11:13:58.721+0800 [正好, 在 mongodump 的过程中]
Step 1 删除还原服务器中的还原库(上面测试遗留的数据库)
Step 2 执行还原命令, 通过 --oplogLimit 参数指定时间点, 时间点为上一步查到的 Timestamp(1527736438, 858)
./mongorestore -h 172.177.XXX.XXX --port 端口 --oplogReplay --oplogLimit "1527736438:858" --authenticationDatabase admin -u 用户名 -p 密码 /data/mongodb_back/mongotestdump
Step 3 结果验证
从 print 出来的执行过程来看, 相同的备份文件, 相同的还原环境, 添加 --oplogLimit "1527736438:858" 参数的 replay Oplog, 要小于上次不带 --oplogLimit 参数的全还原.
本次还原, 添加 --oplogLimit, 只还原了 824 KB 的 oplog
- 2018-05-31T16:55:21.975+0800 replaying oplog
- 2018-05-31T16:55:22.724+0800 oplog 85.9KB
- 2018-05-31T16:55:25.720+0800 oplog 575KB
- 2018-05-31T16:55:27.231+0800 oplog 842KB
- 2018-05-31T16:55:27.231+0800 done
上次, 没有添加 --oplogLimit, 默认全还原, 所以还原了 3.22MB oplog.
- 2018-05-31T11:23:33.770+0800 replaying oplog
- 2018-05-31T11:23:34.682+0800 oplog 146KB
- 2018-05-31T11:23:40.686+0800 oplog 1.10MB
- 2018-05-31T11:23:49.688+0800 oplog 2.61MB
- 2018-05-31T11:23:52.680+0800 oplog 3.11MB
- 2018-05-31T11:23:53.246+0800 oplog 3.22MB
- 2018-05-31T11:23:53.246+0800 done
这也容易理解. 全还原一定大于指定时间的部分还原.
数据检验 :
(1)此时最大 ID 为 "id" : 9718.0, 和参数值对应的 ID 值连续
(2)此过程共还原数据 9719, 低于全还原的 63862
本场景测试结论:
使用参数 --oplogReplay -oplogLimit 可以将数据库还原到指定时刻.
3.3 场景三 oplog.rs 集合中导出数据进行还原
在场景 2 中的 oplog.bson 是我们在 mongodump 全库时, 添加参数 --oplog 参数后自动生成的. 全库备份, 受限于性能损耗和资源限制, 不能高频率执行和长时间存储. 考虑到副本集的 oplog 保存在 oplog.rs 集合中, 此轮测试验证是否只从 oplog.rs 导出, 就可以保证数据连续性. 即从 oplog.rs 导出的数据是否可以替代 mongodump 过程中产生的 oplog.bson.
类似于我们从 oplog.rs 集合中导出 MySQL 的 binlog 文件, 也类似于 SQL Server Log 日志, 可以实现增量备份与增量还原.
Step 1 在上述场景中继续测试, 我们已经执行全库还原, 只是时间点还原到 "id" : 9718.0 对应的数据.
Step 2 删除 上次全备过程中产生的备份文件
Step 3 为增加试验效果, 再次在源库插入部分数据
- for(var i = 0; i < 10000; i++)
- {
- db.order0531.insert({
- a: i
- });
- };
- for(i=0;i<200000;i++)
- {
- db.orderdetial.insert({
- "id":i,"name":"shenzheng","addr":"龙岗","date":new Date()
- });
- };
step 4 将数据库 local 中集合 oplog.rs 导出 (建议导出时, 增加时间参数, 例如一小时内的或大于某时间时刻. 注意, 此处的测试场景中, 我们选择的 Timestamp(1527552239,1), 在我们场景 2 中的 Timestamp(1527736438, 858) 的前面, 故意时间重叠)
./mongodump -h 172.177.XXX.XXX --port 端口 --authenticationDatabase admin -u 用户名 -p 密码 -d local -c oplog.rs --query '{ts:{$gte:Timestamp(1527552239,1)}}' -o /data/mongodb_back/mongotestoplog
step 5 将备份产生的文件 local/oplog.rs.bson,copy 至还原路径下, 并将其命名为 oplog.bson
cp ./mongotestoplog/local/oplog.rs.bson ./mongotestdump/oplog.bson
step 6 执行还原命令
./mongorestore -h 172.177.XXX.XXX --port 端口 --oplogReplay --authenticationDatabase admin -u 用户名 -p 密码 /data/mongodb_back/mongotestdump
step 7 数据验证
还原库
源库
两者数据一致
本场景测试结论:
(1)dumprestor 命令, 可以接受从别处而来, 除了 --oplog 之外, 可人为获取的从 oplog.rs 中导出; 还原时需重命名(step 5).
(2)可以实现数据库的增量备份与增量还原. 我们可以设置 Job, 每小时从集合 oplog.rs 中导出一份操作日志. 然后再利用这些文件中进行还原. 注意此处的增量, 是针对数据变化的, 不是针对上次的全库备份. 此概念与关系型数据库中的增量备份还原不同, 其实更像关系型数据库中的日志备份和还原.
(3)oplog 有一个非常重要的特性 -- 幂等性(idempotent). 即对一个数据集合, 使用 oplog 中记录的操作重放时, 无论被重放多少次, 其结果会是一样的. 举例来说, 如果 oplog 中记录的是一个插入操作, 并不会因为你重放了两次, 数据库中就得到两条相同的记录.
四 总结
1. MongoDB 不支持事务, 无法保证备份还原命令中的事务完整性, 业务一致性(无关系数型据库中基于事务日志的重做还原机制). 但结合命令参数 --oplog, 可以实现数据, 业务的时间一致性.
2. 数据库还原时, 结合参数 --oplogReplay --oplogLimit 实现指定时间点的还原.
3. 搭建副本集的 MongoDB, 定期导出 oplog.rs, 可以实现增量备份与增量还原.
来源: https://www.cnblogs.com/xuliuzai/p/9832333.html