(一)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" } }
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" }
補 充:使用者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" ] })
【完】