• [ 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 "33[31;1m*****[ Mongodb ] 全库恢复脚本*****33[0m"
    host=192.168.118.16
    mongo_bin=/mongodb/bin/
    backpath='/mongodb/backup'
    
    
    echo -e "33[32;1m[ 选择要恢复全库的日期 ] 33[0m"
    for backfile in `ls $backpath`; do
        echo $backfile
    done
    
    read -p ">>>" date_bak
    
    if [[ $date_bak == "" ]] || [[ $date_bak == '.' ]] || [[ $date_bak == '..' ]]; then 
        echo -e "33[31;1m输入不能为特殊字符.33[0m"
        exit 1
    fi
    
    
    if [ -d $backpath/$date_bak ];then
        read -p "请确认是否恢复全库备份[y/n]:" choice
    
        if [ "$choice" == "y" ];then
            echo -e "33[32;1m正在恢复全库备份,请稍后...33[0m"
            $mongo_bin/mongorestore --host $host --port 27017 --oplogReplay --gzip $backpath/$date_bak/
            if [ $? -eq 0 ];then
                echo -e "33[32;1m--------全库恢复成功.--------33[0m"
            else
                echo -e "33[31;1m恢复失败,请手动检查!33[0m"
                exit 3
            fi
        else
            exit 2
        fi
    else
        echo "33[31;1m输入信息错误.33[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 "33[31;1m*****[ Mongodb ] 增量恢复脚本*****33[0m"
    
    echo -e "33[32;1m[ 选择要恢复增量的日期(格式:年月日时分秒) ] 33[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 "33[31;1m输入不能为特殊字符.33[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 "33[32;1m--------[$date_bak]增量恢复成功.--------33[0m"
            else
                echo -e "33[31;1m恢复失败,请手动检查!33[0m"
                exit 3
            fi
        else
            exit 2
        fi
    else
        echo -e "33[31;1m输入信息错误.33[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 周执行一次,增量备份每天执行一次。

      后期准备在建立一个备用的副本集,每天都会将备份数据还原到 新建的副本集中,进行数据备份的校验,准备还是通过脚本来实现。后期在编写。

  • 相关阅读:
    【42.38%】【BZOJ 3196】二逼平衡树
    【7.89%】【BNUOJ 52303】Floyd-Warshall
    【37.38%】【codeforces 722C】Destroying Array
    【57.14%】【codeforces 722B】Verse Pattern
    【26.34%】【codeforces 722A】Broken Clock
    HNOI2008越狱(快速幂)
    HNOI2010弹飞绵羊
    JSOI2008最大数(线段树)
    ZJ2008树的统计(树链剖分)
    SDOI 2010 and SXOI 2014 地精部落 (递推)
  • 原文地址:https://www.cnblogs.com/hukey/p/11512062.html
Copyright © 2020-2023  润新知