[ Mongodb ] 全量備份和增量備份


1. 前言

由於線上的mongodb 數據體量越來越大,如果沒有完善的備份方案,發生故障勢必造成業務很長時間的暫停。參考了網上方案,寫出以下總結和備份方案:

備份方案分為兩種:全備和增量備份,二者結合起來使用。

 參考鏈接:https://www.cnblogs.com/xuliuzai/p/9917137.html  感謝作者的分享。

2. 構建mongodb 副本集測試環境

首先在測試環境進行測試,過程如下:

  測試機:192.168.118.16  系統:CentOS 7

首先搭建mongodb 副本集(為了和線上環境保持一致)這里使用 mongodb 3.6 的版本,建議和生產環境相同的版本。

Mongdb 沒啥安裝的, 開箱即用。副本集參考鏈接:https://www.cnblogs.com/hukey/p/5769548.html

rs0:PRIMARY> rs.isMaster();
{
	"hosts" : [
		"192.168.118.16:27017",
		"192.168.118.16:27018",
		"192.168.118.16:27019"
	],
… 
…

副本集創建成功。

接下來,向集群里寫入數據:

rs0:PRIMARY> for(var i=1;i<=10000;i++) db.users.insert({id:i, name:"hukey",city:"xi'an"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> show dbs;
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> db.users.count();
10000

 寫入了 1W 條數據。

准備工作完成。

 

3. mongodb 全量備份及恢復

全備腳本 [ mongodb_back_all.sh ] 如下:

#!/bin/bash
# Author:hukey

host='192.168.118.16'
port='27017'
sourcepath='/mongodb/bin'
targetpath='/mongodb/backup'
nowtime=$(date "+%Y%m%d")



start(){
    $sourcepath/mongodump --host $host --port $port --oplog --gzip --out ${targetpath}/${nowtime}
}

execute(){
echo "=========================$(date) backup all mongodb back start  ${nowtime}========="
start
if [ $? -eq 0 ];then
    echo "The MongoDB BackUp Successfully!"
else
    echo "The MongoDB BackUp Failure"
fi
}

if [ ! -d "${targetpath}/${nowtime}" ];then
    mkdir -p "${targetpath}/${nowtime}"
fi

execute

backtime=$(date -d '-7 days' "+%Y%m%d")
if [ -d "${targetpath}/${backtime}/" ];then
    rm -rf "${targetpath}/${backtime}/"
    echo "=======${targetpath}/${backtime}/===刪除完畢=="
fi

echo "========================= $(date) backup all mongodb back end ${nowtime}========="

 

全庫還原腳本 [ mongodb_restore_all.sh ] 如下:

#!/bin/bash
# Author:hukey

echo -e "\033[31;1m*****[ Mongodb ] 全庫恢復腳本*****\033[0m"
host=192.168.118.16
mongo_bin=/mongodb/bin/
backpath='/mongodb/backup'


echo -e "\033[32;1m[ 選擇要恢復全庫的日期 ] \033[0m"
for backfile in `ls $backpath`; do
    echo $backfile
done

read -p ">>>" date_bak

if [[ $date_bak == "" ]] || [[ $date_bak == '.' ]] || [[ $date_bak == '..' ]]; then 
    echo -e "\033[31;1m輸入不能為特殊字符.\033[0m"
    exit 1
fi


if [ -d $backpath/$date_bak ];then
    read -p "請確認是否恢復全庫備份[y/n]:" choice

    if [ "$choice" == "y" ];then
        echo -e "\033[32;1m正在恢復全庫備份,請稍后...\033[0m"
        $mongo_bin/mongorestore --host $host --port 27017 --oplogReplay --gzip $backpath/$date_bak/
        if [ $? -eq 0 ];then
            echo -e "\033[32;1m--------全庫恢復成功.--------\033[0m"
        else
            echo -e "\033[31;1m恢復失敗,請手動檢查!\033[0m"
            exit 3
        fi
    else
        exit 2
    fi
else
    echo "\033[31;1m輸入信息錯誤.\033[0m"
    exit 1
fi

 

執行全量備份腳本:

[root@192.168.118.16 /mongodb/script]#sh mongodb_back_all.sh 
=========================Thu Sep 12 11:57:32 CST 2019 backup all mongodb back start  20190912=========
2019-09-12T11:57:32.863+0800	writing admin.system.version to 
2019-09-12T11:57:32.866+0800	done dumping admin.system.version (1 document)
2019-09-12T11:57:32.867+0800	writing test.users to 
2019-09-12T11:57:32.955+0800	done dumping test.users (10000 documents)
2019-09-12T11:57:32.956+0800	writing captured oplog to 
2019-09-12T11:57:32.975+0800		dumped 1 oplog entry
The MongoDB BackUp Successfully!
========================= Thu Sep 12 11:57:32 CST 2019 backup all mongodb back end 20190912=========

 

查看備份數據:

[root@192.168.118.16 /mongodb/script]#ls /mongodb/backup/20190912/
admin  oplog.bson  test

 

在測試全庫還原之前,首先需要清庫數據(注意:本次操作是在測試環境)

rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
10000
rs0:PRIMARY> db.dropDatabase();
{
	"dropped" : "test",
	"ok" : 1,
	"operationTime" : Timestamp(1568264437, 2),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1568264437, 2),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}

 

執行全庫恢復腳本:

[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_all.sh 
*****[ Mongodb ] 全庫恢復腳本*****
[ 選擇要恢復全庫的日期 ] 
20190912
>>>20190912
請確認是否恢復全庫備份[y/n]:y
正在恢復全庫備份,請稍后...
2019-09-12T13:43:58.956+0800	preparing collections to restore from
2019-09-12T13:43:58.959+0800	reading metadata for test.users from /mongodb/backup/20190912/test/users.metadata.json.gz
2019-09-12T13:43:59.019+0800	restoring test.users from /mongodb/backup/20190912/test/users.bson.gz
2019-09-12T13:44:01.950+0800	[#################.......]  test.users  28.0KB/37.9KB  (73.9%)
2019-09-12T13:44:04.083+0800	[########################]  test.users  37.9KB/37.9KB  (100.0%)
2019-09-12T13:44:04.083+0800	no indexes to restore
2019-09-12T13:44:04.084+0800	finished restoring test.users (10000 documents)
2019-09-12T13:44:04.084+0800	replaying oplog
2019-09-12T13:44:04.084+0800	done
--------全庫恢復成功.--------

 

全庫的備份和還原已經實現,可以通過 crontab 來制定計划任務觸發。

 

4. mongodb 增量備份及恢復

增量備份的思路是通過 oplog 來實現的,大家可以通過文檔搜索了解。
直接上腳本:

增量備份 [ mongodb_backup_incremental.sh ] 腳本

#!/bin/bash
# Author:hukey
command_linebin='/mongodb/bin/mongo'
port=27017

if [ ! -d "/mongodb/backup/mongodbOplog_bak/mongo-$port" ];then
    mkdir -p /mongodb/backup/mongodbOplog_bak/mongo-$port
fi

if [ ! -d "/mongodb/backup/mongodbOplog_bak/log-$port" ];then
    mkdir -p /mongodb/backup/mongodbOplog_bak/log-$port
fi

bkdatapath=/mongodb/backup/mongodbOplog_bak/mongo-$port
bklogpath=/mongodb/backup/mongodbOplog_bak/log-$port

logfilename=$(date +"%Y%m%d")

echo "===MongoDB 端口為" $port "的差異備份開始,開始時間為" $(date -d today +"%Y%m%d%H%M%S")

paramBakEndDate=$(date +%s)
echo "===本次備份時間參數中的結束時間為:" $paramBakEndDate

diffTime=$(expr 65 \* 60)
echo "===備份設置的間隔時間為:" $diffTime

paramBakStartDate=$(expr $paramBakEndDate - $diffTime)
echo "===本次備份時間參數中的開始時間為:" $paramBakStartDate

diffTime=$(expr 61 \* 60)
paramAfterBakRequestStartDate=$(expr $paramBakEndDate - $diffTime)
echo "===為保證備份的連續性,本次備份后,oplog中的開始時間需小於:" $paramAfterBakRequestStartDate

bkfilename=$(date -d today +"%Y%m%d%H%M%S")

command_line="${command_linebin} 192.168.118.16:27017"

opmes=$(/bin/echo "db.printReplicationInfo()" | $command_line --quiet)

echo $opmes > /tmp/opdoctime$port.tmplog
opbktmplogfile=/tmp/opdoctime$port.tmplog
opstartmes=$(grep "oplog first event time" $opbktmplogfile | awk -F 'CST' '{print $1}' | awk -F 'oplog first event time: '  '{print $2}' | awk -F ' GMT' '{print $1}'  )
oplogRecordFirst=$(date -d "$opstartmes"  +%s)
echo "===oplog集合記錄的開始時間為[格式化]:" $oplogRecordFirst
if [ $oplogRecordFirst -le $paramBakStartDate ]; then
    echo "Message --檢查設置備份時間合理。備份參數的開始時間在oplog記錄的時間范圍內。"
else 
    echo "Fatal Error --檢查設置的備份時間不合理合理。備份參數的開始時間不在oplog記錄的時間范圍內。請調整oplog size或調整備份頻率。本次備份可以持續進行,但還原時數據完整性丟失。"
fi

/mongodb/bin/mongodump -h 192.168.118.16 --port $port  -d local -c oplog.rs  --query '{ts:{$gte:Timestamp('$paramBakStartDate',1),$lte:Timestamp('$paramBakEndDate',9999)}}' -o $bkdatapath/mongodboplog$bkfilename


opmes=$(/bin/echo "db.printReplicationInfo()" | $command_line --quiet)
echo $opmes > /tmp/opdoctime$port.tmplog
opbktmplogfile=/tmp/opdoctime$port.tmplog
opstartmes=$(grep "oplog first event time" $opbktmplogfile | awk -F 'CST' '{print $1}' | awk -F 'oplog first event time: '  '{print $2}' | awk -F ' GMT' '{print $1}'  )
oplogRecordFirst=$(date -d "$opstartmes"  +%s)
echo "===執行備份后,oplog集合記錄的開始時間為[時間格式化]:" $oplogRecordFirst

if [ $oplogRecordFirst -le $paramAfterBakRequestStartDate ]; then
    echo "Message --備份后,檢查oplog集合中數據的開始時間,即集合中最早的一筆數據,時間不小於61分鍾的時間(即參數 paramAfterBakRequestStartDate)。這樣可以保證每個增量備份含有最近一個小時的全部op操作,滿足文件的持續完整性,逐個還原無丟失數據風險。"
else
    echo "Fatal Error --備份后,檢查oplog集合的涵蓋的時間范圍過小(小於61min)。設置的備份時間不合理合理,備份后的文件不能完全涵蓋最近60分鍾的數據。請調整oplog size或調整備份頻率。本次備份可以持續進行,但還原時數據完整性丟失。"
fi

if [ -d "$bkdatapath/mongodboplog$bkfilename" ]
then
    echo "Message --檢查此次備份文件已經產生.文件信息為:" $bkdatapath/mongodboplog$bkfilename >> $bklogpath/$logfilename.log
else 
    echo "Fatal Error --備份過程已執行,但是未檢測到備份產生的文件,請檢查!" >> $bklogpath/$logfilename.log
fi

keepbaktime=$(date -d '-3 days' "+%Y%m%d%H")*
if [ -d $bkdatapath/mongodboplog$keepbaktime ]; then
    rm -rf $bkdatapath/mongodboplog$keepbaktime
    echo "Message -- $bkdatapath/mongodboplog$keepbaktime 刪除完畢" >> $bklogpath/$logfilename.log
fi

echo "===MongoDB 端口為" $port "的差異備份結束,結束時間為:" $(date -d today +"%Y%m%d%H%M%S")

 

這個腳本比較長,需要注意一個變量: diffTime

第一次定義這個變量的時候,是為了定義備份的時長,從此刻到之前 65 * 60ms 之前的時間,也就是備份從現在到之前 1小時5分這段時間的增量;

第二次定義這個變量的時候,是為了避免數據增長過快,覆蓋了還未備份的數據的,比較的依據是 mongodb db.printReplicationInfo(); 的 oplog first event time 時間。

這兩個定義的時間可靈活調整。

 

增量備份還原 [ mongodb_backup_incremental.sh ] 腳本

#!/bin/bash
# Author:hukey

host=192.168.118.16
port=27017
mongo_bin=/mongodb/bin/
backpath='/mongodb/backup/mongodbOplog_bak/mongo-27017'


echo -e "\033[31;1m*****[ Mongodb ] 增量恢復腳本*****\033[0m"

echo -e "\033[32;1m[ 選擇要恢復增量的日期(格式:年月日時分秒) ] \033[0m"
for time_file in `ls $backpath`; do
    echo $time_file
done

read -p ">>>" date_bak
if [[ $date_bak == "" ]] || [[ $date_bak == '.' ]] || [[ $date_bak == '..' ]]; then 
    echo -e "\033[31;1m輸入不能為特殊字符.\033[0m"
    exit 1
fi
if [ -d $backpath/$date_bak ]; then
    read -p "請確認是否恢復[$date_bak]增量備份[y/n]:" choice
    if [ "$choice" == "y" ];then
        mkdir -p /tmp/mongodb/ && cp -a $backpath/$date_bak/local/oplog.rs.bson /tmp/mongodb/oplog.bson
        $mongo_bin/mongorestore --host $host --port $port --oplogReplay /tmp/mongodb/ && rm -rf /tmp/mongodb/
        if [ $? -eq 0 ];then
            echo -e "\033[32;1m--------[$date_bak]增量恢復成功.--------\033[0m"
        else
            echo -e "\033[31;1m恢復失敗,請手動檢查!\033[0m"
            exit 3
        fi
    else
        exit 2
    fi
else
    echo -e "\033[31;1m輸入信息錯誤.\033[0m"
    exit 1
fi

 

測試下增量備份腳本和還原增量腳本

 

(1)首先寫入一批數據到 mongodb

rs0:PRIMARY> for(var i=1;i<=100;i++) db.users.insert({id:i,name:"hukey"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
100

寫入了 100 條數據成功。

 

(2)執行增量備份腳本

[root@192.168.118.16 /mongodb/script]#sh mongodb_backup_incremental.sh 
===MongoDB 端口為 27017 的差異備份開始,開始時間為 20190912141545
===本次備份時間參數中的結束時間為: 1568268945
===備份設置的間隔時間為: 3900
===本次備份時間參數中的開始時間為: 1568265045
===為保證備份的連續性,本次備份后,oplog中的開始時間需小於: 1568265285
===oplog集合記錄的開始時間為[格式化]: 1568260252
Message --檢查設置備份時間合理。備份參數的開始時間在oplog記錄的時間范圍內。
2019-09-12T14:15:48.648+0800	Failed: error connecting to db server: no reachable servers
===執行備份后,oplog集合記錄的開始時間為[時間格式化]: 1568260252
Message --備份后,檢查oplog集合中數據的開始時間,即集合中最早的一筆數據,時間不小於61分鍾的時間(即參數 paramAfterBakRequestStartDate)。這樣可以保證每個增量備份含有最近一個小時的全部op操作,滿足文件的持續完整性,逐個還原無丟失數據風險。
===MongoDB 端口為 27017 的差異備份結束,結束時間為: 20190912141548

 

(3)再次新增 100 條數據到 mongodb

rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> for(var i=1;i<=100;i++) db.users.insert({id:i,name:"xiaofei"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.users.count();
200

 

(4)再次執行增量備份

[root@192.168.118.16 /mongodb/script]#sh mongodb_backup_incremental.sh 
===MongoDB 端口為 27017 的差異備份開始,開始時間為 20190912141728
===本次備份時間參數中的結束時間為: 1568269048
===備份設置的間隔時間為: 3900
===本次備份時間參數中的開始時間為: 1568265148
===為保證備份的連續性,本次備份后,oplog中的開始時間需小於: 1568265388
===oplog集合記錄的開始時間為[格式化]: 1568260252
Message --檢查設置備份時間合理。備份參數的開始時間在oplog記錄的時間范圍內。
2019-09-12T14:17:31.696+0800	Failed: error connecting to db server: no reachable servers
===執行備份后,oplog集合記錄的開始時間為[時間格式化]: 1568260252
Message --備份后,檢查oplog集合中數據的開始時間,即集合中最早的一筆數據,時間不小於61分鍾的時間(即參數 paramAfterBakRequestStartDate)。這樣可以保證每個增量備份含有最近一個小時的全部op操作,滿足文件的持續完整性,逐個還原無丟失數據風險。
===MongoDB 端口為 27017 的差異備份結束,結束時間為: 20190912141731

 

到此,已知:

第一次增量備份前,數據是 100 條

第二次增量備份前,數據是 200 條

 

(5)刪除 mongodb 數據,恢復第一次增量備份數據

rs0:PRIMARY> db.users.count();
200
rs0:PRIMARY> db.dropDatabase();
{
	"dropped" : "test",
	"ok" : 1,
	"operationTime" : Timestamp(1568270110, 2),
	"$clusterTime" : {
		"clusterTime" : Timestamp(1568270110, 2),
		"signature" : {
			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
			"keyId" : NumberLong(0)
		}
	}
}
rs0:PRIMARY> 
rs0:PRIMARY> show dbs;
admin   0.000GB
config  0.000GB
local   0.001GB

 

執行恢復增量備份的腳本:

[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_incremental.sh 
*****[ Mongodb ] 增量恢復腳本*****
[ 選擇要恢復增量的日期(格式:年月日時分秒) ] 
mongodboplog20190912144121
mongodboplog20190912144403
>>>mongodboplog20190912144121
請確認是否恢復[mongodboplog20190912144121]增量備份[y/n]:y
2019-09-12T14:44:34.534+0800	preparing collections to restore from
2019-09-12T14:44:34.534+0800	replaying oplog
2019-09-12T14:44:37.531+0800	oplog  1.12MB
2019-09-12T14:44:40.021+0800	oplog  1.79MB
2019-09-12T14:44:40.021+0800	done
--------[mongodboplog20190912144121]增量恢復成功.--------

 

此時,mongodb 應該是 100 條數據才對,驗證下:

rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
100

 

數據正確,執行第二次增量備份的還原:

[root@192.168.118.16 /mongodb/script]#sh mongodb_restore_incremental.sh 
*****[ Mongodb ] 增量恢復腳本*****
[ 選擇要恢復增量的日期(格式:年月日時分秒) ] 
mongodboplog20190912144121
mongodboplog20190912144403
>>>mongodboplog20190912144403
請確認是否恢復[mongodboplog20190912144403]增量備份[y/n]:y
2019-09-12T14:45:41.886+0800	preparing collections to restore from
2019-09-12T14:45:41.887+0800	replaying oplog
2019-09-12T14:45:44.882+0800	oplog  1.04MB
2019-09-12T14:45:47.882+0800	oplog  1.75MB
2019-09-12T14:45:50.341+0800	oplog  1.81MB
2019-09-12T14:45:50.341+0800	done
--------[mongodboplog20190912144403]增量恢復成功.--------

 

此時,mongodb 應該是 200 條數據才對,驗證下:

rs0:PRIMARY> use test;
switched to db test
rs0:PRIMARY> db.users.count();
200

 

正確,數據恢復沒有問題。增量備份完成。

 

5. 后記

對於線上生產環境,目前的備份解決方案是:

  全量備份 1 周執行一次,增量備份每天執行一次。

  后期准備在建立一個備用的副本集,每天都會將備份數據還原到 新建的副本集中,進行數據備份的校驗,准備還是通過腳本來實現。后期在編寫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM