欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

程序员文章站 2022-04-01 19:38:00
一 需求描述 我们知道数据是公司的重要资产,业务的系统化、信息化就是数字化。数据高效的存储与查询是系统完善和优化的方向,而数据库的稳定性、可靠性是实现的基础。高可用和RPO(RecoveryPointObjective,复原点目标,指能容忍的最大数据丢失量)是衡量一个数据库优劣的重要指标。作为一个D ......

一 需求描述

我们知道数据是公司的重要资产,业务的系统化、信息化就是数字化。数据高效的存储与查询是系统完善和优化的方向,而数据库的稳定性、可靠性是实现的基础。高可用和rpo(recoverypointobjective,复原点目标,指能容忍的最大数据丢失量)是衡量一个数据库优劣的重要指标。作为一个dba,搭建数据库可靠性体系时,一定会要考虑对数据库进行容灾备份。例如,sql server类型的数据库,我们一定会部署作业,定期进行完整备份、差异备份和日志备份;mysql 数据库同样如此,也是定期进行完整备份、binlog备份等。

可能很多公司的dba认为自己的数据库已采用了新的高可用方案,是多结点冗余了,不再需要冗余备份了,例如sql server 的alwayson,mysql的mha。可是,我们还是要强调两点。

1.墨菲定律:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择。

过往无数的惨痛教训说明,将损失放大的原因就是备份数据也损坏了(大家不重视,实际上很可能就没做)。

2.容灾备份不仅仅可以解决物理故障,还可以将一些其它误操作回滚,将数据的损害降至最低。

数据容灾备份是为数据的灾难恢复加固了最后一道保障墙。

国务院信息化工作办公室领导编制的《重要信息系统灾难恢复指南》也对灾难恢复能力等级做了详细划分。

 

     灾难恢复能力等级

rto

                     rpo

1

2天以上

1天至7天

2

24小时以后

1天至7天

3

12小时以上

数小时至1天

4

数小时至2天

数小时至1天

5

数分钟至2天

0至30分钟

6

数分钟

0

 

随着mongodb使用的越来越普及,存储的数据越来越重要,对其进行定期备份很有必要。现在业内普遍流行的做法是每天定时进行一次全库备份,出故障时,进行全库还原。但是一天一次的备份,很难保证恢复后的数据时效性,rpo较差,会有几个小时的数据丢失。例如,每天5点进行完整备份,如果故障点是晚上20:00,那么就会丢失15个小时的数据。对比上面的“灾难恢复能力等级“”列表,会发现,我们的灾难能力等级比较低。

如果每小时做一次全部备份,那么对存储空间的要求较高,还有就是对性能也会有影响。所以,探究mongodb的增量备还与原很有必要!!!

二 原理说明

关系型数据库,例如mysql ,sql server 都有事务日志(或bin log),会将数据库的dml 、ddl、dcl等操作记录在事务文件中,可以通过日志备份来搭建增量容灾还原体系。mongodb没有此类机制和数据文件,难以实现。但是mongodb副本集有通过oplog(位于local数据库oplog.rs集合中) 实现节点间的同步,此集合记录了整个mongod实例在一段时间内的所有变更(插入/更新/删除)操作。基于此,是否可以考虑通过oplog.rs集合的备份还原来实现实例的增量备份与增量还原。

查看mongodb备份命令mongodump,其中有一个相关参数oplog。

mongodump --oplog参数

参数

参数说明

--oplog

use oplog for taking a point-in-time snapshot

 

该参数的主要作用是在导出库集合数据的同时生成一个oplog.bson文件,里面存放了开始进行dump到dump结束之间所有的op log 操作。

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

 

注意:--oplog选项只对全库导出有效。

相应的 mongorestore 中与 oplog 相关的参数

参数

参数说明

oplogreplay

replay oplog for point-in-time restore

oploglimit

only include oplog entries before the provided timestamp

oplogfile

oplog file to use for replay of oplog

 

 

 仔细观察oplogreplay参数下的还原过程,我们发现,是先还原数据库文件,再重放还原oplog.bson种的数据。这就启发了我们,如果还原路径下,只有oplog.bson文件,没有数据库备份文件,是不是只进行重放还原操作。如此,如果oplog.bson中记录都是上次备份后的变化操作(op log),还原oplog.bson就可以实现了增量还原。考虑到副本集的变化操作(op log)保存在oplog.rs集合中,只要连续从oplog.rs导出操作的相关数据进行备份,就可以实现增量备份。

即理论上,从oplog.rs导出的数据完全可以替代mongodump过程中产生的oplog.bson,进行增量还原。

实际生产中经过多次验证,也是完全可以。

-----具体原理及验证内容还可参考本人博客 --https://www.cnblogs.com/xuliuzai/p/9832333.html

三 代码实现【重点推荐

在容灾体系建设中,既有全库的完整备份也有增量备份。下面是我们的实现代码,因为mongodb多是在linux系统下部署,所以这些备份代码都是通过shell语言实现。这些代码大家只要稍微改动,调整部分参数,即可部署应用。

1. 实现完整(全库)备份的代码

#!/bin/bash
sourcepath='/data/mongodb/mongobin344/bin'
targetpath='/data/mongodb_back/bkxxx_2xxxx'
nowtime=$(date "+%y%m%d")
start()
{
${sourcepath}/mongodump --host 172.xxx.xxx.xxx --port 2xxxx -u 用户名-p "密码" --oplog --gzip --authenticationdatabase "admin" --out ${targetpath}/${nowtime}
}
execute()
{

echo "================================ $(date) bakxxx 2xxxx mongodb back start  ${nowtime}========="

start
if [ $? -eq 0 ]
then
echo "the mongodb backup successfully!"
else "the mongodb backup failure"
fi
}
if [ ! -d "${targetpath}/${nowtime}/" ]
then
  mkdir ${targetpath}/${nowtime}
fi
execute

baktime=$(date -d '-3 days' "+%y%m%d")
if [ -d "${targetpath}/${baktime}/" ]
then
  rm -rf "${targetpath}/${baktime}/"
  echo "=======${targetpath}/${baktime}/===删除完毕=="
fi

echo "================================ $(date) bakxxx 2xxxx mongodb back end ${nowtime}========="

 

代码说明:

1.完整备份的脚本,通过crontab触发执行,每天执行一次。

2.备份完整后,会将三天前的备份文件自动删除。

3.sourcepath 定义了mongodb 运行程序所在路径;targetpath定义了归档文件存放的文件夹(请提前创建)。

 

2. 实现增量备份的代码

#
# this file is used by cron to backup the data of oplog collection,the collection is part of local db.
# the oplog (operations log) is a special capped collection that keeps a rolling record of all operations 
# that modify the data stored in your databases.all replica set members contain a copy of the oplog, 
# in the local.oplog.rs collection, which allows them to maintain the current state of the database.
# each operation in the oplog is idempotent. that is, oplog operations produce the same results 
# whether applied once or multiple times to the target dataset.
#
# we backup the collections by periodicity to restore the db  in case of  db disaster 
# the version is defined v.001
# version   modifytime                modifyby              desc
# ver001    2018-11-06 17:00         xuchangpei             create the scripts file
#
#
#!/bin/bash

#### 请在此处输入关键参数,例如程序路径,账号,密码,实例端口###
command_linebin="/data/mongodb/mongobin344/bin/mongo"
username="用户名"
password="用户命名"
port="mongo都被的端口号"
####
####comments0 start 第一次运行此脚本时,自动检查创建备份路径 ####
if [ ! -d "/data/mongodb_back/mongodboplog_back/mongo$port" ]
then
  mkdir -p /data/mongodb_back/mongodboplog_back/mongo$port
fi

if [ ! -d "/data/mongodb_back/mongodboplog_back/log/$port" ]
then
  mkdir -p /data/mongodb_back/mongodboplog_back/log/$port
fi

bkdatapath=/data/mongodb_back/mongodboplog_back/mongo$port
bklogpath=/data/mongodb_back/mongodboplog_back/log/$port

####comments end ##

logfilename=$(date -d today +"%y%m%d")

echo "===================================message --=mongodb 端口为" $port "的差异备份开始,开始时间为" $(date -d today +"%y%m%d%h%m%s") >> $bklogpath/$logfilename.log

parambakenddate=$(date +%s)
echo "message --本次备份时间参数中的结束时间为:" $parambakenddate >> $bklogpath/$logfilename.log

difftime=$(expr 65 \* 60)

echo "message --备份设置的间隔时间为:" $difftime >> $bklogpath/$logfilename.log


parambakstartdate=$(expr $parambakenddate - $difftime)
echo "message --本次备份时间参数中的开始时间为:" $parambakstartdate >> $bklogpath/$logfilename.log

bkfilename=$(date -d today +"%y%m%d%h%m%s")

#### comments1 start 获取数据库中oplog记录的开始范围,防止导出的数据不完整 ####

command_line="${command_linebin} localhost:$port/admin -u$username -p$password"

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

echo $opmes > opdoctime$port.tmplog

opbktmplogfile=opdoctime$port.tmplog

#opstartmes=$(grep "oplog first event time" $opmes)

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}'  )

echo "message --oplog集合记录的开始时间为:"$opstartmes >> $bklogpath/$logfilename.log

oplogrecordfirst=$(date -d "$opstartmes"  +%s)

echo "message --oplog集合记录的开始时间为:" $oplogrecordfirst >> $bklogpath/$logfilename.log

##begin 比较备份参数的开始时间是否在oplog记录的时间范围内
if [ $oplogrecordfirst -le $parambakstartdate ]
then
echo "message --检查设置备份时间合理。备份参数的开始时间在oplog记录的时间范围内。" >> $bklogpath/$logfilename.log
else echo "fatal error --检查设置的备份时间不合理合理。备份参数的开始时间不在oplog记录的时间范围内。请调整oplog size或调整备份频率。本次备份可以持续进行,但还原时数据完整性丢失。" >> $bklogpath/$logfilename.log
fi

##end##

#### comments1 end  ####

dumpmsg=$(/data/mongodb/mongobin344/bin/mongodump -h localhost --port $port --authenticationdatabase admin -u$username -p$password -d local -c oplog.rs  --query '{ts:{$gte:timestamp('$parambakstartdate',1),$lte:timestamp('$parambakenddate',9999)}}' -o $bkdatapath/mongodboplog$bkfilename)

echo "本次导出的具体信息如下:" $dumpmsg
echo $dumpmsg >> $bklogpath/$logfilename.log

#### comments2 start  再次检查,防止导出oplog数据过程耗时过长,比如,我们一小时导出一份,每一次循环涵盖65分钟,如果导出执行过程耗时5分钟以上就可能导致导出的数据不完整。####
## 下面的70 是有上面的65+5而得,+5 是允许导出耗时5分钟。这个逻辑有点绕,大家可以测测,这段逻辑看几分钟可以理解通透了。
difftime=$(expr 70 \* 60)
allowmaxdate=$(expr $(date +%s) - $difftime)
if [ $allowmaxdate -le $parambakstartdate ]
then
echo "message --oplog记录导出时间在规定的difftime范围内。数据有效" >> $bklogpath/$logfilename.log
else echo "fatal error --oplog记录导出时间 超出了 规定的difftime范围。数据完整性等不到保证。请增大difftime参数或调整备份频率。" >> $bklogpath/$logfilename.log
fi

#### comments2 end ####

#### comments3 检查备份文件是否已经删除start ####
if [ -d "$bkdatapath/mongodboplog$bkfilename" ]
then
  echo "message --检查此次备份文件已经产生.文件信息为:" $bkdatapath/mongodboplog$bkfilename >> $bklogpath/$logfilename.log
  else echo "fatal error --备份过程已执行,但是未检测到备份产生的文件,请检查!" >> $bklogpath/$logfilename.log
fi
##### comments3 end ####

#### comments4 start 删除历史备份文件,保留3天,如需调整,请在持续设置
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
### comments4 end 


echo "============================message --mongodb 端口为" $port "的差异备份结束,结束时间为:" $(date -d today +"%y%m%d%h%m%s") >> $bklogpath/$logfilename.log

 

代码说明:

1.增量备份的脚本,也是通过crontab触发执行,以上参数未修改前,建议每小时执行一次。

2.备份完整后,会自动检查文件是否产生,并且会将三天前的备份文件删除。

3.脚本会自动检查备份路径,不存在将自动产生。

4.增量导出中开始时间和结束时间是最重要的参数,并且要对参数的合法性、有效性检查。例如,检查oplog的记录是否完全涵盖输入的时间,防止出现希望导出08:00--09:00的数据,但是oplog集合中只有08:30--09:00的数据;防止导出过程耗时过长(例如超过定义的5分钟),导致数据不完整。代码中都会对这些异常进行判断和捕获。

 

四 功能测试验证

1. 测试环境

 

item serverip port user db
源库 172.xxx.xxx.124(primary) 2xxx30 testoplog
备份还原库 172.xxx.xxx.124(primary) 2xxx20

 

2. 完整备份与还原

step 1 在备份前,先向数据库testoplog插入部分数据

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 2 完整备份所有的数据库,执行的代码为上面的完整备份代码(保存到执行文件bkoplogtest_2xxx30),打印出执行过程如下截图

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 3 还原完整备份,执行的代码和打印执行过程如下:

执行的命令:

/data/mongodb/mongobin344/bin/mongorestore -h 172.xxx.xxx.124 --port 2xxx20  --oplogreplay --authenticationdatabase 认证数据库-u 用户名-p '密码' --gzip /data/mongodb_back/testoplogbackfile/20181107

打印出的执行过程:

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 4 检查还原库的情况,检查库(testoplog)、表(testfullbefore01、testfullbefore02、testfullbefore03)是否还原。

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

结论:完整还原后与原库完整备份时数据一致,符合测试预期。

 

 3. 增量备份与还原

 

增量备份与还原的测试案例描述

 

测试案例 第一次增量备份 第一次增量还原 第二次增量备份 第二次增量还原
源库

备份前,新建集合testdiffbk01

并插入10000笔数据

 无操作

备份前,新建集合testdiffbk02

并插入10000笔数据

 无操作
还原库 无操作

还原后,检查testdiffbk01是否存在

以及数据量

 无操作

还原后,检查testdiffbk02是否存在

以及数据量

 

step 1 第一次增量备份前,向源库中插入测试数据

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 2 第一次执行增量备份(执行增量备份的脚本,代码放置在执行文件mongodb_oplogbacktestoplog2xxxx.sh中)

 瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 3 向源库中第二次插入测试数据

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 4 第二次执行增量备份

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

两次增量备份产生的文件在 文件夹 /data/mongodb_back/mongodboplog_back/mongo27230 中,如下图所示:

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 5 将完整备份所在路径下的文件清空,将第一次备份的产生的oplog.rs.bson 文件,copy至此路径下,并重命名为oplog.bson。【即还原第一份增量备份

清空指令:

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

copy+ 重命名指令

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

还原指令:

/data/mongodb/mongobin344/bin/mongorestore -h 172.xxx.xxx.124 --port 2xxx20 --oplogreplay --authenticationdatabase 验证数据库-u 用户名-p '密码' /data/mongodb_back/testoplogbackfile/20181107

 瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 7 验证第一次增量还原的数据,验证测试所用的集合testdiffbk01及数据,与原库第一次增量备份时一致,即已正常还原增量。

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 8 将完整备份所在路径下的文件清空,将第二次增量备份的产生的oplog.rs.bson 文件,copy至此路径下,并重命名为oplog.bson。【即还原第二份增量备份

删除指令

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

copy + 重命名指令

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

还原增量备份的指令【与第一次执行的还原命令完全一样】

/data/mongodb/mongobin344/bin/mongorestore -h 172.xxx.xxx.124 --port 2xxx20  --oplogreplay --authenticationdatabase 验证数据库-u 用户名-p '密码' /data/mongodb_back/testoplogbackfile/20181107

 瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

step 9  验证第二次增量还原的数据,验证测试所用的集合testdiffbk02及数据。结论:与原库第二次增量备份时一致,即已正常还原增量。

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

 此轮测试有完整备份与完整还原,还有两次增量备份月增量,详细演示增量还原方案的可行性和相关代码的可执行性,部署后满足生产所需。

五 注意事项

一定要在还原完整备份的路径下,还原已备份oplog的增量文件。即先将已还原的完整备份文件删除,再将增量备份产生oplog.rs.bson文件copy至路径下,并且重命名为oplog.bson。

如果是在其他路径下,则报错,主要的错误信息为:

2018-11-06t10:24:51.848+0800 checking for collection data in /data/mongodb_back/bkrcs_test/oplog.bson
2018-11-06t10:24:51.848+0800 failed: no oplog file to replay; make sure you run mongodump with --oplog

验证测试,完整备份(全库备份)的文件在 路径  /xxx/xxxx_back/bkrcs_2xxxx/20181105 下 。

如果我们将oplog的增量文件(oplog.rs集合导出的数据)/local/oplog.rs.bson 复制至 /xxx/xxxx_back/bkrcs_test/路径下,并重命名为oplog.bson

执行restore命令报错:

瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

如果我们将/local/oplog.rs.bson复制至还原完整备份所在的路径下( /xxx/xxxx_back/bkrcs_27xxx/20181105),执行restore,测试不再报错。

 瞧一瞧!这儿实现了MongoDB的增量备份与还原(含部署代码)

检查新增数据也已同步过去。

所以,还原时增量备份(oplog)一定要放置完整备份所在的文件夹下(copy前,先将完整备份完结删除)进行还原。

 

本文版权归作者所有,未经作者同意不得转载,谢谢配合!!!

 本文版权归作者所有,未经作者同意不得转载,谢谢配合!!!

本文版权归作者所有,未经作者同意不得转载,谢谢配合!!!