(一)MongoDB恢复概述

对于任何类型的数据库,如果要将数据库恢复到过去的任意时间点,否需要有过去某个时间点的全备+全备之后的重做日志,MongoDB也不例外。使用全备将数据库恢复到固定时刻,然后使用重做日志追加全备之后的操作。

重做日志备份:MongoDB只有开启主从复制或者副本集时才会开启重做日志,主从复制存放在local数据库下的oplog.$main集合中,复制集的日志存放在local数据库下的oplog.rs集合中,该集合是一个上限集合,当达到固定大小时,最老的记录会被自动覆盖。因此需要注意,MongoDB的重做日志并不会一直保存着,能否恢复到故障点,完全取决于日志是否完整。

(二)操作日志oplog

(2.1)oplog日志格式解析


为了查看oplog日志保存了什么信息,向test集合中插入2条数据:

db.test.insert({"empno":1,"ename":"lijiaman","age":22,"address":"yunnan,kungming"});
db.test.insert({"empno":2,"ename":"aaa","age":18,"address":"sichuan,chengdu"});

查看test集合的数据信息

db.test.find()
/* 1 */
{
"_id" : ObjectId("5f30eb58bcefe5270574cd54"),
"empno" : 1.0,
"ename" : "lijiaman",
"age" : 22.0,
"address" : "yunnan,kungming"
} /* 2 */
{
"_id" : ObjectId("5f30eb58bcefe5270574cd55"),
"empno" : 2.0,
"ename" : "aaa",
"age" : 18.0,
"address" : "sichuan,chengdu"
}

使用下面查询语句查看oplog日志信息:

use local
var since = Math.floor(ISODate("2020-08-10T14:00:00.000Z").getTime() / 1000) - 8*60*60
var until = Math.floor(ISODate("2020-08-10T23:00:00.000Z").getTime() / 1000) - 8*60*60
db.oplog.$main.find(
{
$and : [
{"ns" : /lijiamandb.test/},
{"ts" : { "$gt" : Timestamp(since, 1),"$lt":Timestamp(until,1)}}
]
}
).sort({ts:1})

结果如下:

/* 1 */
{
"ts" : Timestamp(1597070283, 1),
"op" : "i",
"ns" : "lijiamandb.test",
"o" : {
"_id" : ObjectId("5f30eb58bcefe5270574cd54"),
"empno" : 1.0,
"ename" : "lijiaman",
"age" : 22.0,
"address" : "yunnan,kungming"
}
} /* 2 */
{
"ts" : Timestamp(1597070283, 2),
"op" : "i",
"ns" : "lijiamandb.test",
"o" : {
"_id" : ObjectId("5f30eb58bcefe5270574cd55"),
"empno" : 2.0,
"ename" : "aaa",
"age" : 18.0,
"address" : "sichuan,chengdu"
}
}

oplog中各个字段的含义:

ts:数据写的时间,括号里面第1位数据代表时间戳,是自unix纪元以来的秒值,第2位代表在1s内订购时间戳的序列数

op:操作类型,可选参数有:
       -- "i": insert
       --"u": update
       --"d": delete
       --"c": db cmd
       --"db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')
       --"n": no op,即空操作,其会定期执行以确保时效性

ns:命名空间,通常是具体的集合

o:具体的写入信息

o2: 在执行更新操作时的where条件,仅限于update时才有该属性

因此,如果要实现MongoDB基于时间点的恢复,只要解析oplog日志,就可以实现操作重做。

(2.2)确认日志保存情况

oplog是一个上限集合,当数据量达到一定大小后,MongoDB会自动清理oplog日志信息,为了保证恢复能够正常进行,需要确认日志的时间是否符合还原需求。简单来说,oplog应该保存着自上一次备份以来的所有日志。可以使用下面2种方法来确认最早的oplog。

方法一:查询oplog中的最小时间

db.oplog.$main.aggregate([{$group:{_id:1,min_salary:{$min:"$ts"}}}])

/* 1 */
{
"_id" : 1.0,
"min_salary" : Timestamp(1595503517, 2)
}

方法二:查看主从复制信息

在主节点查看日志信息,可以看到oplog日志大小,因为oplog是一个固定大小的集合,所以还可以看到日志的开始、结束时间、oplog的时间差等。

> db.printReplicationInfo()
configured oplog size: 2129.547656059265MB
log length start to end: 9180secs (2.55hrs)
oplog first event time: Thu Jun 18 2020 21:43:14 GMT+0800 (CST)
oplog last event time: Fri Jun 19 2020 00:16:14 GMT+0800 (CST)
now: Mon Aug 10 2020 18:59:23 GMT+0800 (CST)


(2.3)备份oplog日志


在使用mongodump备份数据库时,默认是不备份oplog的,需要我们手动去备份,常用的备份方法如下。

(1)备份所有数据库的oplog日志

mongodump --authenticationDatabase admin -uroot -p123456 --db=local --collection='oplog.$main' --out=/root/backup/oplog

(2)备份单个数据库的oplog日志。例如,备份catdb数据库的oplog日志

mongodump --authenticationDatabase admin -uroot -p123456 --db=local --collection='oplog.$main' --query='{"ns":/catdb/}' --out=/root/backup/oplog

(3)备份单个集合的oplog日志。例如,备份catdb.myc1集合的oplog日志

mongodump --authenticationDatabase admin -uroot -p123456 --db=local --collection='oplog.$main' --query='{"ns":"catdb.myc1"}' --out=/root/backup/oplog

(4)使用多个条件来过滤oplog日志

# 备份catdb数据库,且只备份insert操作的oplog日志
mongodump --authenticationDatabase admin -uroot -p123456 --db=local --collection='oplog.$main' --query='{"ns":/catdb/,"op":"i"}' --out=/root/backup/oplog # 备份catdb数据库,且备份在时间Timestamp( 1597241858, 1 )到 Timestamp( 1597242471, 1 ) 之间的数据
# 需要注意,不包含上下限时间
mongodump --authenticationDatabase admin -uroot -p123456 --db=local --collection='oplog.$main' --query='{"ns":/catdb/,"ts" : { "$gt" : Timestamp( 1597241858, 1 ),"$lt":Timestamp(1597242471, 1 )}}' --out=/root/backup/oplog


(三)模拟将MongoDB恢复到任意时间点

(3.1)案例一:将整个实例恢复到某个时间点


(3.3.1)故障场景描述

业务人员发现多个MongoDB数据库均存在数据错误的情况,需要将全部数据恢复到过去的某个时刻。

(3.3.2)数据恢复方法描述

只要确定了恢复时间点,就可以使用完全备份+oplog备份,将数据恢复到过去的某个时刻。

(3.3.3)恢复过程

STEP1:模拟业务正常运行,数据正常进入MongoDB数据库

use db1
db.db1test.insert({id:1,name:'a'})
db.db1test.insert({id:2,name:'b'}) use db2
db.db2test.insert({id:11,name:'aa'})
db.db2test.insert({id:22,name:'bb'})

STEP2:执行完整备份

mongodump --authenticationDatabase admin -uroot -p123456 -o /root/backup/full

STEP3:再次模拟业务正常运行,数据正常进入MongoDB数据库

use db1
db.db1test.insert({id:3,name:'c'}) use db2
db.db2test.insert({id:33,name:'cc'})

最终数据如下:

> use db1
switched to db db1
> db.db1test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26862"), "id" : 1, "name" : "a" }
{ "_id" : ObjectId("5f35110ba27e9a00c0f26863"), "id" : 2, "name" : "b" }
{ "_id" : ObjectId("5f35113ca27e9a00c0f26866"), "id" : 3, "name" : "c" }
>
>
> use db2
switched to db db2
> db.db2test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26864"), "id" : 11, "name" : "aa" }
{ "_id" : ObjectId("5f35110ca27e9a00c0f26865"), "id" : 22, "name" : "bb" }
{ "_id" : ObjectId("5f35113da27e9a00c0f26867"), "id" : 33, "name" : "cc" }
>

STEP4:模拟数据误操作

# db1的db1test集合id增加100
use db1
db.db1test.update({},{$inc:{"id":100}},{multi:true}) # db2的db2test集合被删除
use db2
db.db2test.drop()

错误操作之后的结果:

> use db1
switched to db db1
> db.db1test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26862"), "id" : 101, "name" : "a" }
{ "_id" : ObjectId("5f35110ba27e9a00c0f26863"), "id" : 102, "name" : "b" }
{ "_id" : ObjectId("5f35113ca27e9a00c0f26866"), "id" : 103, "name" : "c" }
>
> use db2
switched to db db2
> db.db2test.find()
>

要求把所有数据库的数据恢复到STEP4之前的状态。

STEP5:停止业务,不再往数据库写数据

STEP6:备份日志。可以备份部分日志,也可以备份全部日志

mongodump --authenticationDatabase admin -uroot -p123456 -d local -c 'oplog.$main' -o /root/backup/oplog/

STEP7:确认数据异常时间点,对oplog集合进行分析

use local
db.oplog.$main.find(
{
$and : [
{"ns" : /db1/},
{"op" : "u" }
]
}
).sort({ts:1})

查询结果如下,可以确认,开始对db1.db1test集合更新的时间为Timestamp(1597313442, 1)

/* 1 */
{
"ts" : Timestamp(1597313442, 1),
"op" : "u",
"ns" : "db1.db1test",
"o2" : {
"_id" : ObjectId("5f35110ba27e9a00c0f26862")
},
"o" : {
"$set" : {
"id" : 101.0
}
}
} /* 2 */
{
"ts" : Timestamp(1597313442, 2),
"op" : "u",
"ns" : "db1.db1test",
"o2" : {
"_id" : ObjectId("5f35110ba27e9a00c0f26863")
},
"o" : {
"$set" : {
"id" : 102.0
}
}
} /* 3 */
{
"ts" : Timestamp(1597313442, 3),
"op" : "u",
"ns" : "db1.db1test",
"o2" : {
"_id" : ObjectId("5f35113ca27e9a00c0f26866")
},
"o" : {
"$set" : {
"id" : 103.0
}
}
}

STEP8:执行完全备份的恢复

需要注意,考虑是否需要使用"--drop"选项,如果不用该选项,会保留集合中当前的数据,如果使用了drop选项,在导入集合时会先删除集合。这里使用该选项

mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 --drop  /root/backup/full/

需要注意权限问题,这里发现使用root账号无法执行恢复,但是使用权限较小的root2账号却可以(备注:关于root和root2用户权限信息,会在文档结尾给出):

[root@mongo1 oplog]# mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 --drop  /root/backup/full/

connected to: 127.0.0.1:27017

2020-08-13T10:25:05.963+0000     going into namespace [admin.system.version]

1 document found

2020-08-13T10:25:05.964+0000     Creating index: { key: { _id: 1 }, name: "_id_", ns: "admin.system.version" }

Error creating index admin.system.version: 13 err: "not authorized to create index on admin.system.version"

Aborted

[root@mongo1 full]# mongorestore --authenticationDatabase admin -uroot2 -p123456 --port=27017 --drop  /root/backup/full/

确认全量恢复的数据,已经恢复回来:

> use db1
switched to db db1
> db.db1test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26862"), "id" : 1, "name" : "a" }
{ "_id" : ObjectId("5f35110ba27e9a00c0f26863"), "id" : 2, "name" : "b" }
>
>
>
> use db2
switched to db db2
> db.db2test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26864"), "id" : 11, "name" : "aa" }
{ "_id" : ObjectId("5f35110ca27e9a00c0f26865"), "id" : 22, "name" : "bb" }
>

STEP9:使用oplog执行增量恢复

在恢复oplog之前,需要对其格式进行处理,否则会报错:

#  报错提示找不到oplog
[root@mongo1 full]# mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 --oplogReplay --oplogLimit "1597313442:1" /root/backup/oplog/local
connected to: 127.0.0.1:27017
No oplog file to replay. Make sure you run mongodump with --oplog.

需要把oplog.$main.metadata.json 文件删除,把oplog.$main.bson名字改为oplog.bson

[root@mongo1 local]# pwd
/root/backup/oplog/local
[root@mongo1 local]# ls
oplog.$main.bson oplog.$main.metadata.json
[root@mongo1 local]# rm -rf oplog.\$main.metadata.json
[root@mongo1 local]# mv oplog.\$main.bson oplog.bson
[root@mongo1 local]# ls
oplog.bson

最后执行oplog增量恢复即可

mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 --oplogReplay --oplogLimit "1597313442:1" /root/backup/oplog/local

注意:这里有一个大坑,需要特别留意,在使用上述命令导入数据时,整个过程没有报错,但是最终数据并没有恢复回来,如下面所示:

整个导入过程没有报错

[root@mongo1 local]# mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 --oplogReplay --oplogLimit "1597313442:1" /root/backup/oplog/local
connected to: 127.0.0.1:27017
2020-08-13T10:33:27.830+0000 Replaying oplog
2020-08-13T10:33:30.013+0000 Progress: 3055430/1353998783 0% (bytes)
2020-08-13T10:33:33.005+0000 Progress: 5632309/1353998783 0% (bytes)
2020-08-13T10:33:36.003+0000 Progress: 8604531/1353998783 0% (bytes)
...
...
2020-08-13T10:44:07.009+0000 Progress: 1340889939/1353998783 99% (bytes)
2020-08-13T10:44:10.004+0000 Progress: 1351348749/1353998783 99% (bytes)
4604171 documents found
2020-08-13T10:44:10.699+0000 Applied 4592833 oplog entries out of 4592837 (4 skipped).

然而数据未恢复回来

> use db1
switched to db db1
> db.db1test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26862"), "id" : 1, "name" : "a" }
{ "_id" : ObjectId("5f35110ba27e9a00c0f26863"), "id" : 2, "name" : "b" }
>
> use db2
switched to db db2
> db.db2test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26864"), "id" : 11, "name" : "aa" }
{ "_id" : ObjectId("5f35110ca27e9a00c0f26865"), "id" : 22, "name" : "bb" }

查询error log日志,发现root用户没有权限执行导入

2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db1 to execute command { applyOps: [ { ts: Timestamp 1597313291000|1, op: "i", ns: "db1.db1test", o: { _id: ObjectId('5f35110ba27e9a00c0f26862'), id: 1.0, name: "a" } } ] }
2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db1 to execute command { applyOps: [ { ts: Timestamp 1597313291000|2, op: "i", ns: "db1.db1test", o: { _id: ObjectId('5f35110ba27e9a00c0f26863'), id: 2.0, name: "b" } } ] }
2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db2 to execute command { applyOps: [ { ts: Timestamp 1597313291000|3, op: "i", ns: "db2.db2test", o: { _id: ObjectId('5f35110ba27e9a00c0f26864'), id: 11.0, name: "aa" } } ] }
2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db2 to execute command { applyOps: [ { ts: Timestamp 1597313292000|1, op: "i", ns: "db2.db2test", o: { _id: ObjectId('5f35110ca27e9a00c0f26865'), id: 22.0, name: "bb" } } ] }
2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db1 to execute command { applyOps: [ { ts: Timestamp 1597313340000|1, op: "i", ns: "db1.db1test", o: { _id: ObjectId('5f35113ca27e9a00c0f26866'), id: 3.0, name: "c" } } ] }
2020-08-13T10:44:10.692+0000 [conn18] Unauthorized not authorized on db2 to execute command { applyOps: [ { ts: Timestamp 1597313341000|1, op: "i", ns: "db2.db2test", o: { _id: ObjectId('5f35113da27e9a00c0f26867'), id: 33.0, name: "cc" } } ] }

处理办法:使用root2用户导入

[root@mongo1 local]# mongorestore --authenticationDatabase admin -uroot2 -p123456 --port=27017 --oplogReplay --oplogLimit "1597313442:1" /root/backup/oplog/local

STEP10:确认数据恢复情况,发现数据以及恢复到了STEP4之前的状态

> use db1
switched to db db1
> db.db1test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26862"), "id" : 1, "name" : "a" }
{ "_id" : ObjectId("5f35110ba27e9a00c0f26863"), "id" : 2, "name" : "b" }
{ "_id" : ObjectId("5f35113ca27e9a00c0f26866"), "id" : 3, "name" : "c" }
>
>
> use db2
switched to db db2
> db.db2test.find()
{ "_id" : ObjectId("5f35110ba27e9a00c0f26864"), "id" : 11, "name" : "aa" }
{ "_id" : ObjectId("5f35110ca27e9a00c0f26865"), "id" : 22, "name" : "bb" }
{ "_id" : ObjectId("5f35113da27e9a00c0f26867"), "id" : 33, "name" : "cc" }
>

至此恢复结束。


(3.2)案例二:误删除某个DB,对单个DB进行恢复


通常,每个DB承载不同的业务,相互之间没有关系,如果出现故障,往往会表现在某个DB上,因此,如果出现故障,只对相应的DB进行恢复,那将减小对业务的影响。

(3.2.1)故障场景描述

假设业务运行过程中,数据库db3被人误删除了,我们需要对db3进行恢复,并且不能影响到其它的DB业务。

(3.2.2)数据恢复方法描述

可以在当前实例上进行恢复,也可以新启动一个mongod实例,用于数据恢复,然后再把确认无误的数据导入到生产环境中,我们采用新的mongod实例来恢复数据。

1.首先新启动一个mongod实例;

2.将已有的完全备份恢复到新的实例上;

3.备份oplog,只备份db3的oplog,其它数据库的不备份;

4.使用oplog将数据库恢复到删除之前;

5.检查db3数据库的数据,确认是否恢复回来;

6.如果第5步没有问题,mongodump导出db3数据库,然后倒入到生产环境中。

(3.2.3)恢复过程

STEP1:模拟业务正常运行,数据正常进入MongoDB数据库

use db3
db.db3test.insert({id:111,name:'aaa'})
db.db3test.insert({id:222,name:'bbb'})
db.db3test.insert({id:333,name:'ccc'})

STEP2:执行完整备份

mongodump --authenticationDatabase admin -uroot -p123456 -o /root/backup/full

STEP3:再次模拟业务正常运行,数据正常进入MongoDB数据库

use db3
db.db3test.insert({id:444,name:'ddd'})
db.db3test.insert({id:555,name:'eee'})
db.db3test.insert({id:666,name:'fff'})

最终数据如下:

> db.db3test.find()
{ "_id" : ObjectId("5f352400a27e9a00c0f2686b"), "id" : 111, "name" : "aaa" }
{ "_id" : ObjectId("5f352400a27e9a00c0f2686c"), "id" : 222, "name" : "bbb" }
{ "_id" : ObjectId("5f352401a27e9a00c0f2686d"), "id" : 333, "name" : "ccc" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686e"), "id" : 444, "name" : "ddd" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686f"), "id" : 555, "name" : "eee" }
{ "_id" : ObjectId("5f352429a27e9a00c0f26870"), "id" : 666, "name" : "fff" }

STEP4:模拟数据误操作

> db
db3 > db.dropDatabase()
{ "dropped" : "db3", "ok" : 1 }

接下来执行恢复操作。

STEP5:在发现误操作之后,我们需要把db3恢复回来,首先应该备份oplog,这里只涉及到db3数据库,只要备份db3的oplog即可,这样可以加快备份恢复速度

mongodump --authenticationDatabase admin -uroot -p123456 -d local -c 'oplog.$main' -q '{"ns":/db3/}'  -o /root/backup/oplog/

STEP6:重新开启一个mongod实例

mongod --port=27018 --dbpath=/tmp/data

STEP7:在新的实例上恢复全备数据,只要恢复db3即可

mongorestore  --port=27018 -d db3 /root/backup/full/db3

确认数据全备恢复情况

# 执行恢复前
> show dbs
admin (empty)
local 0.078GB
> # 执行恢复后,db3数据已经恢复到了全备时的状态
> show dbs
admin (empty)
db3 0.078GB
local 0.078GB
>
>
> use db3
switched to db db3
> show collections
db3test
system.indexes
> db.db3test.find()
{ "_id" : ObjectId("5f352400a27e9a00c0f2686b"), "id" : 111, "name" : "aaa" }
{ "_id" : ObjectId("5f352400a27e9a00c0f2686c"), "id" : 222, "name" : "bbb" }
{ "_id" : ObjectId("5f352401a27e9a00c0f2686d"), "id" : 333, "name" : "ccc" }
>

STEP8:在新的实例上恢复oplog数据,恢复到drop操作之前

先确认drop db3数据库的时间点:  "ts" : Timestamp(1597318247, 1)

use local
db.oplog.$main.find(
{
$and : [
{"ns" : /db3/},
{"op" : "c" }
]
}
).sort({ts:1}) // 结果
{
"ts" : Timestamp(1597318247, 1),
"op" : "c",
"ns" : "db3.$cmd",
"o" : {
"dropDatabase" : 1.0
}
}

执行增量恢复:

# 先处理oplog,删除文件oplog.$main.metadata.json,修改oplog.$main.bson为oplog.bson
[root@mongo1 local]# pwd
/root/backup/oplog/local
[root@mongo1 local]# rm -f oplog.\$main.metadata.json
[root@mongo1 local]# mv oplog.\$main.bson oplog.bson
[root@mongo1 local]# ls
oplog.bson # 执行恢复
mongorestore --port=27018 --oplogReplay --oplogLimit "1597318247:1" /root/backup/oplog/local

检查数据是否已经恢复,可以确认,数据已经恢复回来

> db.db3test.find()
{ "_id" : ObjectId("5f352400a27e9a00c0f2686b"), "id" : 111, "name" : "aaa" }
{ "_id" : ObjectId("5f352400a27e9a00c0f2686c"), "id" : 222, "name" : "bbb" }
{ "_id" : ObjectId("5f352401a27e9a00c0f2686d"), "id" : 333, "name" : "ccc" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686e"), "id" : 444, "name" : "ddd" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686f"), "id" : 555, "name" : "eee" }
{ "_id" : ObjectId("5f352429a27e9a00c0f26870"), "id" : 666, "name" : "fff" }

STEP9:把数据导出再导入到生产环境

# 从新的mongod环境导出db3数据库
[root@mongo1 ~]# mongodump -d db3 --port=27018 -out=/root # 将db3导入到生产环境,这里需要考虑是否用--drop关键字
[root@mongo1 ~]# mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 -d db3 /root/db3

确认数据是否已经导入到生产环境:

> show dbs
admin 0.078GB
catdb 0.078GB
db1 0.078GB
db2 0.078GB
db3 0.078GB
dogdb 0.078GB
lijiamandb 0.078GB
local 4.076GB
mydb 0.078GB
testdb 0.078GB
>
> use db3
switched to db db3
> show collections
db3test
system.indexes
>
> db.db3test.find()
{ "_id" : ObjectId("5f352400a27e9a00c0f2686b"), "id" : 111, "name" : "aaa" }
{ "_id" : ObjectId("5f352400a27e9a00c0f2686c"), "id" : 222, "name" : "bbb" }
{ "_id" : ObjectId("5f352401a27e9a00c0f2686d"), "id" : 333, "name" : "ccc" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686e"), "id" : 444, "name" : "ddd" }
{ "_id" : ObjectId("5f352428a27e9a00c0f2686f"), "id" : 555, "name" : "eee" }
{ "_id" : ObjectId("5f352429a27e9a00c0f26870"), "id" : 666, "name" : "fff" }
>

数据以及全部导入到了生产环境,测试完成。注意,别忘记关闭新建的mongod实例。


(3.3)案例三:误操作某个集合,对单个集合进行恢复


(3.3.1)故障场景描述

业务人员执行误删操DBA对数据进行恢复,详细过程如下:

T1~T2:业务正常运行,数据正常进入数据库

T2:使用mongodump执行数据库完全备份

T2~T4:业务正常运行,数据正常进入数据库

T4:用户误删除数据

T4~T6:业务还在运行,但是已经出现问题,如此时还能正常插入数据,但是查询、更新、删除数据存在找不到数据的错误

T6:DBA介入数据恢复

(3.3.2)数据恢复方法描述

可以在当前实例上进行恢复,也可以新启动一个mongod实例,用于数据恢复,我们在上一个例子中已经使用新建mongod实例的方式来恢复数据,本次实验我们直接在生产实例上进行恢复。

1.执行完全恢复,使用完全备份,将数据库恢复到T2时刻;

2.找到T4时刻故障之前的时间,从而确定T2~T4之间的oplog日志。结合T2时刻的全备+ T2~T4之间的oplog日志,实现数据恢复;(备注:这里不需要去确认T2之后的日志开始时间,在使用oplog恢复数据时,是通过唯一编号“_id”来操作数据的,oplog可能从全备份之前的任意时间开始,但是并不影响数据的正确性)。

3.找到T4时刻故障之后的时间,备份oplog。

4.使用oplog,实现T4~T6时间段的恢复。

(3.3.3)恢复过程

STEP1:模拟业务正常运行,数据正常进入MongoDB数据库

use db4
db.db4test.insert({id:1111,name:'aaaa'})
db.db4test.insert({id:2222,name:'bbbb'})
db.db4test.insert({id:3333,name:'cccc'})

STEP2:执行完整备份

mongodump --authenticationDatabase admin -uroot -p123456 -o /root/backup/full

STEP3:再次模拟业务正常运行,数据正常进入MongoDB数据库

use db4
db.db4test.insert({id:4444,name:'dddd'})
db.db4test.insert({id:5555,name:'eeee'})
db.db4test.insert({id:6666,name:'ffff'})

最终数据如下:

> db.db4test.find()
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26871"), "id" : 1111, "name" : "aaaa" }
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26872"), "id" : 2222, "name" : "bbbb" }
{ "_id" : ObjectId("5f3545c4a27e9a00c0f26873"), "id" : 3333, "name" : "cccc" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26874"), "id" : 4444, "name" : "dddd" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26875"), "id" : 5555, "name" : "eeee" }
{ "_id" : ObjectId("5f354632a27e9a00c0f26876"), "id" : 6666, "name" : "ffff" }

STEP4:模拟数据误操作,删除2条数据

> db.db4test.remove({id:{$gt:4444}})
WriteResult({ "nRemoved" : 2 })
>
> db.db4test.find()
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26871"), "id" : 1111, "name" : "aaaa" }
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26872"), "id" : 2222, "name" : "bbbb" }
{ "_id" : ObjectId("5f3545c4a27e9a00c0f26873"), "id" : 3333, "name" : "cccc" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26874"), "id" : 4444, "name" : "dddd" }

STEP5:再次模拟业务正常运行,数据正常进入MongoDB数据库

use db4
db.db4test.insert({id:7777,name:'gggg'})
db.db4test.insert({id:8888,name:'hhhh'})
db.db4test.insert({id:9999,name:'kkkk'})

最终数据如下:

> db.db4test.find()
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26871"), "id" : 1111, "name" : "aaaa" }
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26872"), "id" : 2222, "name" : "bbbb" }
{ "_id" : ObjectId("5f3545c4a27e9a00c0f26873"), "id" : 3333, "name" : "cccc" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26874"), "id" : 4444, "name" : "dddd" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26877"), "id" : 7777, "name" : "gggg" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26878"), "id" : 8888, "name" : "hhhh" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26879"), "id" : 9999, "name" : "kkkk" }

此时,我们发现id为5555和6666的数据是被误删除的,需要恢复回来,并且要保留执行删除命令之后的数据。

STEP6:在发现误操作之后,首先应该备份oplog,这里只涉及到db4.db4test集合,只要备份该集合的oplog即可,这样可以加快备份恢复速度

mongodump --authenticationDatabase admin -uroot -p123456 -d local -c 'oplog.$main' -q '{"ns":"db4.db4test"}'  -o /root/backup/oplog/

STEP7:对该集合执行完全恢复操作

mongorestore --authenticationDatabase admin -uroot -p123456 --port=27017 -d db4 -c db4test  /root/backup/full/db4/db4test.bson

STEP8:使用oplog,对该集合执行增量恢复操作

先查看对db4.db4test集合执行删除的开始时间

use local
db.oplog.$main.find(
{
$and : [
{"ns" : /db4.db4test/},
{"op" : "d" }
]
}
).sort({ts:1}) // 结果
/* 1 */
{
"ts" : Timestamp(1597326944, 1),
"op" : "d",
"ns" : "db4.db4test",
"b" : true,
"o" : {
"_id" : ObjectId("5f354631a27e9a00c0f26875")
}
} /* 2 */
{
"ts" : Timestamp(1597326944, 2),
"op" : "d",
"ns" : "db4.db4test",
"b" : true,
"o" : {
"_id" : ObjectId("5f354632a27e9a00c0f26876")
}
}

可以看到,删除的开始时间为:Timestamp(1597326944, 1)。

执行增量恢复:

# 先处理oplog,删除文件oplog.$main.metadata.json,修改oplog.$main.bson为oplog.bson
[root@mongo1 local]# pwd
/root/backup/oplog/local
[root@mongo1 local]# rm -f oplog.\$main.metadata.json
[root@mongo1 local]# mv oplog.\$main.bson oplog.bson
[root@mongo1 local]# ls
oplog.bson # 执行恢复,root用户没权限导入,root2用户才有权限
mongorestore --authenticationDatabase admin -uroot2 -p123456 --port=27017 --oplogReplay --oplogLimit "1597326944:1" /root/backup/oplog/local

STEP9:查看数据是否恢复,确认已经完全恢复回来

> db.db4test.find()
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26872"), "id" : 2222, "name" : "bbbb" }
{ "_id" : ObjectId("5f3545c4a27e9a00c0f26873"), "id" : 3333, "name" : "cccc" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26874"), "id" : 4444, "name" : "dddd" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26877"), "id" : 7777, "name" : "gggg" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26878"), "id" : 8888, "name" : "hhhh" }
{ "_id" : ObjectId("5f3546ada27e9a00c0f26879"), "id" : 9999, "name" : "kkkk" }
{ "_id" : ObjectId("5f3545c3a27e9a00c0f26871"), "id" : 1111, "name" : "aaaa" }
{ "_id" : ObjectId("5f354631a27e9a00c0f26875"), "id" : 5555, "name" : "eeee" }
{ "_id" : ObjectId("5f354632a27e9a00c0f26876"), "id" : 6666, "name" : "ffff" }

到此,MongoDB 2.7主从复制环境基于时间点恢复已经测试完成。

补 充:用户root和root2权限信息                                                    

目前在导入数据时,使用具有root权限的超级用户进行数据导入,发现依然存在权限不走的提示。经过stackoverflow上面的提示,创建了root2用户来导入数据,不再报错。

stackoverflow:https://stackoverflow.com/questions/55208028/mongodb-applyops-not-authorized-on-admin-to-execute-command

root用户权限信息如下:具有userAdminAnyDatabase和root角色

> db.getUser("root")
{
"_id" : "admin.root",
"user" : "root",
"db" : "admin",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
},
{
"role" : "root",
"db" : "admin"
}
]
}

root2用户权限信息如下,这里直接给出创建角色和用户的脚本

db.createRole(
{
role: "interalUseOnlyOplogRestore",
privileges: [
{ resource: { anyResource: true }, actions: [ "anyAction" ] }
],
roles: []
}
) db.createUser({
user: "root2",
pwd: "123456",
roles: [
"interalUseOnlyOplogRestore"
]
})

【完】

1.MongoDB 2.7主从复制(master –> slave)环境基于时间点的恢复的更多相关文章

  1. 2.MongoDB 4.2副本集环境基于时间点的恢复

    (一)MongoDB恢复概述 对于任何数据库,如果要将数据库恢复到过去的任意时间点,否需要有过去某个时间点的全备+全备之后的重做日志. 接下来根据瑞丽航空的情况进行概述: 全备:每天晚上都会进行备份: ...

  2. mysql主从复制 master和slave配置的参数大全

    master所有参数1 log-bin=mysql-bin 1.控制master的是否开启binlog记录功能: 2.二进制文件最好放在单独的目录下,这不但方便优化.更方便维护. 3.重新命名二进制日 ...

  3. Redis主从复制(Master/Slave)

    Redis主从复制(Master/Slave) 修改配置文件 拷贝多个redis.conf文件分别配置如下参数: 开启daemonize yes pidfile port logfile dbfile ...

  4. Redis主从复制(Master/Slave) 与哨兵模式

    Redis主从复制是什么? 行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主 Redis主从复制 ...

  5. Redis系列七 主从复制(Master/Slave)

    主从复制(Master/Slave) 1.是什么 也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主. 2 ...

  6. Redis 的主从复制(Master/Slave)

    目录 1. 是什么 2. 能干嘛 3. Redis主从复制讲解 (1). info replication:查看 目标redis 主从情况 (2) . 配从库不配主库 (3). 常用策略 (4). 复 ...

  7. 配置MySQL主从复制报错Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server ids; these ids must be different for replication to work

    配置MySQL主从复制报错 ``` Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave ha ...

  8. mysql主从复制--重置操作reset master, reset slave

    本文介绍reset master, reset slave的作用. reset master 在master上执行 mysql > RESET MASTER 作用包括: 删除binlog索引文件 ...

  9. "Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs

    最近在部署MySQL主从复制架构的时候,碰到了"Last_IO_Error: Fatal error: The slave I/O thread stops because master a ...

随机推荐

  1. Spark入门(第1讲)

    一.Spark是什么 引用官方文档的一句话 Apache Spark is a unified analytics engine for large-scale data processing. Ap ...

  2. PHP fileowner() 函数

    定义和用法 fileowner() 函数返回指定文件的用户 ID(所有者). 如果成功,该函数返回用户 ID.如果失败,则返回 FALSE. 语法 fileowner(filename) 参数 描述 ...

  3. PHP serialize() 函数

    serialize() 函数用于序列化对象或数组,并返回一个字符串.高佣联盟 www.cgewang.com serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型 ...

  4. asp.net mvc 模拟百度搜索

    页面代码: <td><span>*</span>车牌号码:</td> <td> <div id="search"& ...

  5. 牛客挑战赛 39 牛牛与序列 隔板法 容斥 dp

    LINK:牛牛与序列 (牛客div1的E题怎么这么水... 还没D难. 定义一个序列合法 当且仅当存在一个位置i满足 $a_i>a_,a_j<a_$且对于所有的位置i,$1 \leq a_ ...

  6. windows下使用redis命令行模式查询数据

    背景:redis的火,就像java一样,对于测试人员来说,使用它就需要好好搞下,现在就整理下命令行模式,来查询获取自己想要的值: 命令行连接命令:redis-cli -h 主机名 -p 端口号 -a ...

  7. 实验02——java两个数交换的三种解决方案

    package cn.tedu.demo;/** * @author 赵瑞鑫      E-mail:1922250303@qq.com * @version 1.0* @创建时间:2020年7月16 ...

  8. JS 模仿京东键盘输入内容

    css代码 .search { width: 300px; height: 80px; margin: 0 auto; position: relative; } .con { display: no ...

  9. 正确认识springcloud的作用。分布式从了解架构到springcloud支撑

    转载于 https://www.cnblogs.com/williamjie/p/9369681.html 基于springCloud的分布式架构体系   Spring Cloud作为一套微服务治理的 ...

  10. Eclipse工具的简单使用

    前言 虽然编写Java用Idea比较好,但是对于正处于大学阶段的我,还是要和老师的步伐保持一致,但是,用的Idea这个工具多了,我就感觉对eclipse这个工具不是怎么熟悉了,甚至还有点对一些工具的使 ...