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

109次阅读

共计 3872 个字符,预计需要花费 10 分钟才能阅读完成。

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

案例介绍

某用户应用了 MongoDB 4.0,数据库中的一个表因为 drop 操作导致数据全副失落。但因为库自身很小,而 oplog 空间足够大,所以从建库至今的所有操作都尚在 oplog 中没有被回收。基于这种状况,尽管他们没有全量备份,咱们依然能够通过残缺重放 oplog 来找回所有失落的数据。所以咱们的操作是:

导出 oplog;

寻找 drop 产生的工夫戳;

重放到 drop 前一刻;

将复原的数据 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 dump
dump
└── 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 empty
mongorestore --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 认证专家,已经为不同行业的各类大型客户提供过培训、性能调优、架构设计等各类技术及咨询服务,颇得广大客户信赖。

正文完
 0