(一)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. 常用限流算法与Guava RateLimiter源码解析

    在分布式系统中,应对高并发访问时,缓存.限流.降级是保护系统正常运行的常用方法.当请求量突发暴涨时,如果不加以限制访问,则可能导致整个系统崩溃,服务不可用.同时有一些业务场景,比如短信验证码,或者其它 ...

  2. OFDM通信系统的MATLAB仿真(2)

    关于OFDM系统的MATLAB仿真实现的第二篇随笔,在第一篇中,我们讨论的是信号经过AWGN信道的情况,只用添加固定噪声功率的高斯白噪声就好了.但在实际无线信道中,信道干扰常常是加性噪声.多径衰落的结 ...

  3. pycharm控制台输出的日志全是红色的字体?

    问题:logging在pycharm控制台输出的日志的字体全是红色的,怎么办? 图片描述: 解决办法:设置 -> 搜索“Console” ->  结果:改完立马生效

  4. python的__get__方法看这一篇就足够了

    get类型函数 直接上代码: class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 if __name__ ...

  5. Spring+hibernate+JSP实现Piano的数据库操作---1.目录结构+展示

    目录结构 界面

  6. c++输出左右对齐设置

    #include<iostream> int main(){ using std::cout; cout.setf(std::ios::left); int w = cout.width( ...

  7. Fortify Audit Workbench Cookie Security: Cookie not Sent Over SSL

    Abstract 所创建的 cookie 的 secure 标记没有设置为 true. Explanation 现今的 Web 浏览器支持每个 cookie 的 secure 标记. 如果设置了该标记 ...

  8. Python爬虫开发与项目实战pdf电子书|网盘链接带提取码直接提取|

    Python爬虫开发与项目实战从基本的爬虫原理开始讲解,通过介绍Pthyon编程语言与HTML基础知识引领读者入门,之后根据当前风起云涌的云计算.大数据热潮,重点讲述了云计算的相关内容及其在爬虫中的应 ...

  9. 对于AES和RSA的个人理解

    最近学习爬虫 遇到一些加密的坑 然后了解到了AES和RSA  记录一下 AES 1.什么是AES AES是一种对称的加密算法,运行要求低,不需要计算机有非常高的处理能力和大的内存, 加密速度很快: 对 ...

  10. Python time strftime()方法

    描述 Python time strftime() 函数接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数format决定.高佣联盟 www.cgewang.com 语法 strftime( ...