写点什么

MongoDB 案例分享:如何使用 oplog 恢复数据

  • 2022 年 3 月 11 日
  • 本文字数:2881 字

    阅读完需:约 9 分钟

MongoDB案例分享:如何使用oplog恢复数据


最近跟数据恢复杠上了,这不又来一例。关于备份恢复的问题其实我在 6 年多以前就写过,其中大部分讨论放在今天仍然适用。


案例介绍


某用户使用了 MongoDB 4.0,数据库中的一个表因为drop操作导致数据全部丢失。但因为库本身很小,而oplog空间足够大,所以从建库至今的所有操作都尚在oplog中没有被回收。基于这种情况,虽然他们没有全量备份,我们仍然可以通过完整重放oplog来找回所有丢失的数据。所以我们的操作是:

  1. 导出oplog

  2. 寻找drop发生的时间戳;

  3. 重放到drop前一刻;

  4. 将恢复的数据 dump/restore 到生产库;

步骤 4 属于基本操作就不详细叙述了,主要来看前面 3 步。


恢复步骤


2.1 导出 oplog

这一步实际上特别简单。oplog位于local.oplog.rs集合中,我们可以使用mongodump直接导出,导出节点可以是主节点或从节点。基本形式是:

mongodump --host <host>:<port> -d local -c oplog.rs -u <user> --authenticationDatabase <adb>
复制代码

得到如下输出:

> tree dumpdump└── local    ├── oplog.rs.bson    └── oplog.rs.metadata.json
1 directory, 2 files
复制代码

我们需要的就是oplog.rs.bson


2.2 寻找截止时间戳

进行重放的关键是要先找出重放截止到哪条oplog。这里有两种办法:

方案 1

oplog.rs.bson中搜索关键字drop

> bsondump dump/local/oplog.rs.bson | grep drop{"ts":{"$timestamp":{"t":1646056645,"i":1}},"t":{"$numberLong":"1"},"h":{"$numberLong":"7307295890643732087"},"v":{"$numberInt":"2"},"op":"c","ns":"test.$cmd","ui":{"$binary":{"base64":"9sakiEOMS2qjwBZ5O0mQjQ==","subType":"04"}},"wall":{"$date":{"$numberLong":"1646056645661"}},"o":{"drop":"survey"}}
复制代码

如果多次出现drop记录,则要自己注意辨别哪条是你想要的那条。然后注意记录中{"t":1646056645,"i":1}是我们要截止到的时间戳,后面将会用到这个数据。另外注意如果oplog较多时该办法可能会耗时较长。

方案 2

local.oplog.rs中查询。这种查询方法通常会比方案 1 快,但需要在原始系统上运行查询,可能造成一定的负担。如果系统本身压力已经较大,则要注意避开业务高峰期。另外也可以在从节点上执行查询以避开压力最大的主节点。这里要注意的是每个节点上保存的 oplog 可能不一样多,但一定是一致的。例如,某个节点上的oplog有 1,2,3,4,5 共计 5 条,其他节点上可能只有:

  • 2,3,4,5

  • 3,4,5

  • 4,5

  • 5

这种情况通常是由于从节点是后来加进集群里导致的。那么想要查询时,可以使用:

> use local> db.oplog.rs.find({"o.drop": {$exists: true}}).sort({$natural: -1}).limit(1);{ "ts" : Timestamp(1646056729, 1), "t" : NumberLong(1), "h" : NumberLong("6882491835596436855"), "v" : 2, "op" : "c", "ns" : "test.$cmd", "ui" : UUID("a98cba5a-066b-46fe-92a9-d122386dba5d"), "wall" : ISODate("2022-02-28T13:58:49.167Z"), "o" : { "drop" : "survey" } }
复制代码

同样注意Timestamp(1646056729, 1)是我们将要用到的截止时间戳。


2.3 重放 oplog

mongorestore本身是用来恢复bson文件的同时顺便重放oplog的。现在我们没有bson要恢复,只有oplog要重放,所以需要点小花招来欺骗mongorestore,那就是用一个空文件夹:

mkdir emptymongorestore --host <host>:<port> -u <user> --authenticationDatabase <adb> \  --oplogReplay \  --oplogFile dump/local/oplog.rs.bson \  --oplogLimit 1646056729:1 \  empty/
复制代码


注意:这里应该在一个新的实例上完成重放操作。重放完成后,你就拥有了一份截止到drop操作前的完整数据。


改进方案


上面的步骤虽然可以完成任务,但有些浪费。因为丢失的只有一个表,我们却恢复了整个数据库,消耗了不必要的时间。有没有办法只恢复丢失的那一个表呢?从原理来讲是可以办到的,那就是只重放这个表上的oplog,那么只需要在导出oplog的时候做个过滤就可以办到了:

mongodump --host <host>:<port> -d local -c oplog.rs -u <user> --authenticationDatabase <adb> -q '{"ns": "test.survey"}'
复制代码


后续步骤没有什么差异,就不再赘述了。但是这样的做法有个 bug,那就是事务。我一开始也栽在了这个问题上。事务会把多条操作放在一条oplog里,以此来保证事务的原子性。比如如下事务操作:

var mongo = db.getMongo();var session = mongo.startSession();session.startTransaction();var coll = session.getDatabase("test").getCollection("survey");coll.insertOne({y: 1});coll.insertOne({y: 2});coll.insertOne({y: 3});session.commitTransaction();
复制代码


其产生的oplog是这样的:

{    "ts": Timestamp(1646057834, 1),    "t": NumberLong(1),    "h": NumberLong("-2362908976881142089"),    "v": 2,    "op": "c",    "ns": "admin.$cmd",    "wall": ISODate("2022-02-28T14:17:14.189Z"),    "lsid": {        "id": UUID("02ca1f7e-f451-4ec3-946f-cf307c0d03b7"),        "uid": BinData(0, "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=")    },    "txnNumber": NumberLong(1),    "stmtId": 0,    "prevOpTime": {        "ts": Timestamp(0, 0),        "t": NumberLong(-1)    },    "o": {        "applyOps": [{            "op": "i",            "ns": "test.survey",            "ui": UUID("04a8b634-4048-48a6-b358-9a879c1a20ed"),            "o": {                "_id": ObjectId("621cd969a3a94c2e74b595c5"),                "y": 1            }        }, {            "op": "i",            "ns": "test.survey",            "ui": UUID("04a8b634-4048-48a6-b358-9a879c1a20ed"),            "o": {                "_id": ObjectId("621cd969a3a94c2e74b595c6"),                "y": 2            }        }, {            "op": "i",            "ns": "test.survey",            "ui": UUID("04a8b634-4048-48a6-b358-9a879c1a20ed"),            "o": {                "_id": ObjectId("621cd969a3a94c2e74b595c7"),                "y": 3            }        }]    }}
复制代码


可见这里的{"ns": "admin.$cmd"}并不在test.survey上,所以上面的过滤办法会把事务产生的数据都排除在外,就会造成一部分数据丢失。解决办法也很简单,修改一下过滤条件:

mongodump --host <host>:<port> -d local -c oplog.rs -u <user> --authenticationDatabase <adb> -q '{"$or": [{"ns": "test.survey"}, {"o.applyOps.ns": "test.survey"}]}'
复制代码


结束语


这个案例是个很极端的情况,所以不要想着抄作业,你几乎一定不会遇到相同的场景。但恢复的原理却是相通的,无论何种备份恢复都是“全量”+“增量”的做法,只要你理解了原理,剩下的就是动手尝试而已。


关于作者:张耀星


MongoDB 中文社区常委会委员,论坛联席主席。

MongoDB 公司北亚区首席技术咨询服务顾问。在 MongoDB 的开发、应用和咨询服务方面,拥有多年的丰富实践经验。

作为 MongoDB 认证专家,曾经为不同行业的各类大型客户提供过培训、性能调优、架构设计等各类技术及咨询服务,颇得广大客户信任。

用户头像

还未添加个人签名 2019.07.27 加入

还未添加个人简介

评论

发布
暂无评论
MongoDB案例分享:如何使用oplog恢复数据_mongodb_MongoDB中文社区_InfoQ写作平台