庆云 發表於 2019-9-12 15:18:00

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

<h2>1. 前言</h2>
<p>由于线上的mongodb 数据体量越来越大,如果没有完善的备份方案,发生故障势必造成业务很长时间的暂停。参考了网上方案,写出以下总结和备份方案:</p>
<p>备份方案分为两种:全备和增量备份,二者结合起来使用。</p>
<p>&nbsp;参考链接:https://www.cnblogs.com/xuliuzai/p/9917137.html&nbsp; 感谢作者的分享。</p>
<h2>2. 构建mongodb 副本集测试环境</h2>
<p>首先在测试环境进行测试,过程如下:</p>
<p>  测试机:192.168.118.16&nbsp; 系统:CentOS 7</p>
<p>首先搭建mongodb 副本集(为了和线上环境保持一致)这里使用 mongodb 3.6 的版本,建议和生产环境相同的版本。</p>
<p>Mongdb 没啥安装的, 开箱即用。副本集参考链接:https://www.cnblogs.com/hukey/p/5769548.html</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; rs.isMaster();
{
        "hosts" : [
                "192.168.118.16:27017",
                "192.168.118.16:27018",
                "192.168.118.16:27019"
        ],


</pre>
</div>
<p>副本集创建成功。</p>
<p>接下来,向集群里写入数据:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; for(var i=1;i&lt;=10000;i++) db.users.insert({id:i, name:"hukey",city:"xi'an"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY&gt; show dbs;
admin   0.000GB
config0.000GB
local   0.000GB
test    0.000GB
rs0:PRIMARY&gt; use test
switched to db test
rs0:PRIMARY&gt; db.users.count();
10000
</pre>
</div>
<p>&nbsp;写入了 1W 条数据。</p>
<p>准备工作完成。</p>
<p>&nbsp;</p>
<h2>3. mongodb 全量备份及恢复</h2>
<p>全备脚本<span style="background-color: rgba(0, 255, 0, 1)"> [ mongodb_back_all.sh ]</span> 如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#!/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}========="
</pre>
</div>
<p>&nbsp;</p>
<p>全库还原脚本<span style="background-color: rgba(0, 255, 0, 1)"> [ mongodb_restore_all.sh ]</span> 如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#!/bin/bash
# Author:hukey

echo -e "\033 全库恢复脚本*****\033[0m"
host=192.168.118.16
mongo_bin=/mongodb/bin/
backpath='/mongodb/backup'


echo -e "\033 \033[0m"
for backfile in `ls $backpath`; do
    echo $backfile
done

read -p "&gt;&gt;&gt;" 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 "请确认是否恢复全库备份:" 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
</pre>
</div>
<p>&nbsp;</p>
<p>执行全量备份脚本:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#sh mongodb_back_all.sh
=========================Thu Sep 12 11:57:32 CST 2019 backup all mongodb back start20190912=========
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=========
</pre>
</div>
<p>&nbsp;</p>
<p>查看备份数据:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#ls /mongodb/backup/20190912/
adminoplog.bsontest
</pre>
</div>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 0, 1)">在测试全库还原之前,首先需要清库数据(注意:本次操作是在测试环境)</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; use test;
switched to db test
rs0:PRIMARY&gt; db.users.count();
10000
rs0:PRIMARY&gt; db.dropDatabase();
{
        "dropped" : "test",
        "ok" : 1,
        "operationTime" : Timestamp(1568264437, 2),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1568264437, 2),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}
</pre>
</div>
<p>&nbsp;</p>
<p>执行全库恢复脚本:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#sh mongodb_restore_all.sh
*****[ Mongodb ] 全库恢复脚本*****
[ 选择要恢复全库的日期 ]
20190912
&gt;&gt;&gt;20190912
请确认是否恢复全库备份: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.users28.0KB/37.9KB(73.9%)
2019-09-12T13:44:04.083+0800        [########################]test.users37.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
--------全库恢复成功.--------
</pre>
</div>
<p>&nbsp;</p>
<p>全库的备份和还原已经实现,可以通过 crontab 来制定计划任务触发。</p>
<p>&nbsp;</p>
<h2>4. mongodb 增量备份及恢复</h2>
<p>增量备份的思路是通过 oplog 来实现的,大家可以通过文档搜索了解。<br>直接上脚本:</p>
<p>增量备份<span style="background-color: rgba(0, 255, 0, 1)"> [ mongodb_backup_incremental.sh ]</span> 脚本</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#!/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 60 \* 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 &gt; /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 &gt; /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 &gt;&gt; $bklogpath/$logfilename.log
else
    echo "Fatal Error --备份过程已执行,但是未检测到备份产生的文件,请检查!" &gt;&gt; $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 删除完毕" &gt;&gt; $bklogpath/$logfilename.log
fi

echo "===MongoDB 端口为" $port "的差异备份结束,结束时间为:" $(date -d today +"%Y%m%d%H%M%S")
</pre>
</div>
<p>&nbsp;</p>
<p>这个脚本比较长,需要注意一个变量: <span style="background-color: rgba(255, 255, 0, 1)">diffTime</span></p>
<p>第一次定义这个变量的时候,是为了定义备份的时长,从此刻到之前 65 * 60ms 之前的时间,也就是备份从现在到之前 1小时5分这段时间的增量;</p>
<p>第二次定义这个变量的时候,是为了避免数据增长过快,覆盖了还未备份的数据的,比较的依据是 mongodb db.printReplicationInfo(); 的 oplog first event time 时间。</p>
<p>这两个定义的时间可灵活调整。</p>
<p>&nbsp;</p>
<p>增量备份还原<span style="background-color: rgba(0, 255, 0, 1)"> [ mongodb_backup_incremental.sh ]</span> 脚本</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#!/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 增量恢复脚本*****\033[0m"

echo -e "\033 \033[0m"
for time_file in `ls $backpath`; do
    echo $time_file
done

read -p "&gt;&gt;&gt;" 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]增量备份:" choice
    if [ "$choice" == "y" ];then
      mkdir -p /tmp/mongodb/ &amp;&amp; cp -a $backpath/$date_bak/local/oplog.rs.bson /tmp/mongodb/oplog.bson
      $mongo_bin/mongorestore --host $host --port $port --oplogReplay /tmp/mongodb/ &amp;&amp; rm -rf /tmp/mongodb/
      if [ $? -eq 0 ];then
            echo -e "\033增量恢复成功.--------\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
</pre>
</div>
<p>&nbsp;</p>
<p>测试下增量备份脚本和还原增量脚本</p>
<p>&nbsp;</p>
<p>(1)首先写入一批数据到 mongodb</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; for(var i=1;i&lt;=100;i++) db.users.insert({id:i,name:"hukey"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY&gt; use test;
switched to db test
rs0:PRIMARY&gt; db.users.count();
100
</pre>
</div>
<p>写入了 100 条数据成功。</p>
<p>&nbsp;</p>
<p>(2)执行增量备份脚本</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#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
</pre>
</div>
<p>&nbsp;</p>
<p>(3)再次新增 100 条数据到 mongodb</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; use test
switched to db test
rs0:PRIMARY&gt; for(var i=1;i&lt;=100;i++) db.users.insert({id:i,name:"xiaofei"});
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY&gt; db.users.count();
200
</pre>
</div>
<p>&nbsp;</p>
<p>(4)再次执行增量备份</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#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
</pre>
</div>
<p>&nbsp;</p>
<p>到此,已知:</p>
<p>第一次增量备份前,数据是 100 条</p>
<p>第二次增量备份前,数据是 200 条</p>
<p>&nbsp;</p>
<p>(5)删除 mongodb 数据,恢复第一次增量备份数据</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; db.users.count();
200
rs0:PRIMARY&gt; 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&gt;
rs0:PRIMARY&gt; show dbs;
admin   0.000GB
config0.000GB
local   0.001GB
</pre>
</div>
<p>&nbsp;</p>
<p>执行恢复增量备份的脚本:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#sh mongodb_restore_incremental.sh
*****[ Mongodb ] 增量恢复脚本*****
[ 选择要恢复增量的日期(格式:年月日时分秒) ]
mongodboplog20190912144121
mongodboplog20190912144403
&gt;&gt;&gt;mongodboplog20190912144121
请确认是否恢复增量备份: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        oplog1.12MB
2019-09-12T14:44:40.021+0800        oplog1.79MB
2019-09-12T14:44:40.021+0800        done
--------增量恢复成功.--------
</pre>
</div>
<p>&nbsp;</p>
<p>此时,mongodb 应该是 100 条数据才对,验证下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; use test;
switched to db test
rs0:PRIMARY&gt; db.users.count();
100
</pre>
</div>
<p>&nbsp;</p>
<p>数据正确,执行第二次增量备份的还原:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">#sh mongodb_restore_incremental.sh
*****[ Mongodb ] 增量恢复脚本*****
[ 选择要恢复增量的日期(格式:年月日时分秒) ]
mongodboplog20190912144121
mongodboplog20190912144403
&gt;&gt;&gt;mongodboplog20190912144403
请确认是否恢复增量备份: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        oplog1.04MB
2019-09-12T14:45:47.882+0800        oplog1.75MB
2019-09-12T14:45:50.341+0800        oplog1.81MB
2019-09-12T14:45:50.341+0800        done
--------增量恢复成功.--------
</pre>
</div>
<p>&nbsp;</p>
<p>此时,mongodb 应该是 200 条数据才对,验证下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:bash;gutter:true;">rs0:PRIMARY&gt; use test;
switched to db test
rs0:PRIMARY&gt; db.users.count();
200
</pre>
</div>
<p>&nbsp;</p>
<p>正确,数据恢复没有问题。增量备份完成。</p>
<p>&nbsp;</p>
<h2>5. 后记</h2>
<p>对于线上生产环境,目前的备份解决方案是:</p>
<p>  全量备份 1 周执行一次,增量备份每天执行一次。</p>
<p>  后期准备在建立一个备用的副本集,每天都会将备份数据还原到 新建的副本集中,进行数据备份的校验,准备还是通过脚本来实现。后期在编写。</p><br><br>
来源:https://www.cnblogs.com/hukey/p/11512062.html
頁: [1]
查看完整版本: [ Mongodb ] 全量备份和增量备份