引言:为什么MongoDB备份至关重要
在当今数据驱动的世界中,数据库备份是保障业务连续性的关键环节。MongoDB作为最流行的NoSQL数据库之一,虽然具备高可用性和容错能力,但仍然面临硬件故障、人为误操作、恶意攻击等多种数据丢失风险。一套完善的备份策略不仅能防止数据丢失,还能确保在灾难发生时能够快速恢复业务。
本文将全面解析MongoDB的备份策略,从基础的mongodump工具使用,到高级的自动化备份方案,涵盖单机部署、副本集和分片集群等不同场景,帮助您构建可靠的数据保护体系。
一、MongoDB备份基础:mongodump工具详解
1.1 mongodump工作原理
mongodump是MongoDB官方提供的逻辑备份工具,它通过连接到MongoDB服务器,读取集合数据并导出为BSON格式文件。与物理备份相比,mongodump具有以下特点:
- 逻辑备份:导出的是数据的逻辑表示,而非磁盘上的物理文件
- 跨平台/版本兼容:导出的数据可以在不同平台和MongoDB版本间迁移
- 选择性备份:可以按数据库、集合甚至查询条件进行部分备份
- 性能影响:在备份过程中会读取数据库,对线上业务有一定性能影响
1.2 基础备份命令与参数详解
1.2.1 备份整个实例
# 备份所有数据库(需要管理员权限)
mongodump --host localhost --port 27017 --username admin --password "yourpassword" --authenticationDatabase admin --out /backup/mongodb/$(date +%Y%m%d)
参数说明:
--host:MongoDB服务器地址--port:MongoDB服务器端口--username:数据库用户名--password:用户密码--authenticationDatabase:用户认证数据库--out:备份输出目录,这里使用日期作为子目录名
1.2.2 备份指定数据库
# 备份单个数据库
mongodump --db myapp --out /backup/mongodb/myapp_$(date +%Y%m%d)
# 备份多个数据库(用逗号分隔)
mongodump --db myapp,test --out /backup/mongodb/multi_$(date +%Y%m%d)
1.2.3 备份指定集合
# 备份单个集合
mongodump --db myapp --collection users --out /backup/mongodb/users_$(date +%Y%m%d)
# 备份多个集合
mongodump --db myapp --collection users,orders --out /backup/mongodb/collections_$(date +%Y%m%d)
1.2.4 带查询条件的备份
# 只备份符合条件的文档
mongodump --db myapp --collection users --query '{"status":"active","last_login":{"$gte":{"$date":"2023-01-01T00:00:00Z"}}}' --out /backup/mongodb/active_users_$(date +%Y%m%d)
查询条件说明:
- 使用JSON格式指定查询条件
- 日期格式需要使用
{"$date": "ISO日期字符串"} - 支持所有MongoDB查询操作符
1.2.5 增量备份策略
MongoDB本身不支持增量备份,但可以通过以下方式实现:
# 1. 首次全量备份
mongodump --oplog --out /backup/mongodb/full_$(date +%Y%m%d)
# 2. 后续增量备份(利用oplog)
# 注意:增量备份需要配合oplog窗口期,确保在两次备份之间oplog未被覆盖
# 3. 恢复时需要按顺序应用oplog
mongorestore --oplogReplay --oplogLimit "2023-12-01T12:00:00Z" /backup/mongodb/full_20231201
重要提示:
--oplog参数用于创建具有时间点一致性的备份- 增量备份需要精确控制oplog窗口,适合有固定oplog保留时间的环境
- 生产环境建议使用更可靠的WAL归档或Percona Backup for MongoDB
1.3 mongorestore恢复工具详解
1.3.1 基础恢复命令
# 恢复整个备份
mongorestore --host localhost --port 27017 --username admin --password "yourpassword" --authenticationDatabase admin /backup/mongodb/20231201
# 恢复指定数据库
mongorestore --db myapp /backup/mongodb/20231201/myapp
# 恢复指定集合
mongorestore --db myapp --collection users /backup/mongodb/20231201/myapp/users.bson
1.3.2 高级恢复选项
# 恢复时重命名数据库
mongorestore --nsFrom "myapp.*" --nsTo "myapp_restore.*" /backup/mongodb/20231201
# 恢复时忽略索引(加快恢复速度,后续再重建索引)
mongorestore --noIndexRestore /backup/mongodb/20231201
# 恢复时使用多个线程加速
mongorestore --numInsertionWorkersPerCollection 4 --batchSize 1000 /backup/mongodb/20231201
# 恢复oplog(实现时间点恢复)
mongorestore --oplogReplay --oplogLimit "2023-12-01T12:00:00Z" /backup/mongodb/20231201
1.3.3 恢复验证
# 恢复后验证数据完整性
mongosh --eval "
db.adminCommand({listDatabases:1}).databases.forEach(function(db){
var colls = db.getSiblingDB(db.name).getCollectionNames();
colls.forEach(function(coll){
if(coll.indexOf('system.') === -1){
var count = db.getSiblingDB(db.name)[coll].countDocuments();
print(db.name + '.' + coll + ': ' + count + ' documents');
}
});
});
"
二、物理备份策略:文件系统快照
2.1 物理备份的优势与适用场景
物理备份直接复制MongoDB的数据文件(/data/db目录),相比逻辑备份具有以下优势:
- 速度快:直接文件复制,无需解析数据
- 性能影响小:对线上业务影响较小
- 适合大数据量:TB级数据备份效率高
- 恢复快:直接恢复文件,无需数据导入
适用场景:
- 大数据量环境(TB级别)
- 对备份恢复时间要求严格(RTO短)
- 数据库文件位于支持快照的存储系统上
2.2 文件系统快照备份
2.2.1 使用LVM快照(Linux)
# 1. 确保MongoDB数据目录在LVM卷上
# 查看当前LVM信息
sudo lvdisplay
sudo vgdisplay
# 2. 创建一致性快照(需要先锁定数据库)
mongosh --eval "db.fsyncLock()"
# 3. 创建LVM快照(假设数据卷为/dev/vg0/mongo_data)
sudo lvcreate -L 10G -s -n mongo_snap /dev/vg0/mongo_data
# 4. 解锁数据库
mongosh --eval "db.fsyncUnlock()"
# 5. 挂载快照并复制数据
sudo mount /dev/vg0/mongo_snap /mnt/mongo_snapshot
sudo rsync -av /mnt/mongo_snapshot/ /backup/mongodb/snapshot_$(date +%Y%m%d)/
# 6. 清理快照
sudo umount /mnt/mongo_snapshot
sudo lvremove -f /dev/vg0/mongo_snap
2.2.2 使用AWS EBS快照
# 1. 获取MongoDB数据卷的Volume ID
aws ec2 describe-instances --instance-ids i-1234567890abcdef0 \
--query "Reservations[0].Instances[0].BlockDeviceMappings[?DeviceName=='/dev/sdf'].Ebs.VolumeId" \
--output text
# 2. 创建一致性快照(需要先锁定数据库)
mongosh --eval "db.fsyncLock()"
# 3. 创建EBS快照
aws ec2 create-snapshot --volume-id vol-0123456789abcdef0 \
--description "MongoDB backup $(date +%Y%m%d)" \
--tag-specifications 'ResourceType=snapshot,Tags=[{Key=BackupType,Value=Daily},{Key=Environment,Value=Production}]'
# 4. 等待快照完成
aws ec2 wait snapshot-completed --snapshot-ids snap-0123456789abcdef0
# 5. 解锁数据库
mongosh --eval "db.fsyncUnlock()"
# 6. 清理旧快照(保留最近7天)
aws ec2 describe-snapshots --filters "Name=tag:BackupType,Values=Daily" \
--query "Snapshots[?StartTime<='$(date -d '7 days ago' +%Y-%m-%d)'].SnapshotId" \
--output text | xargs -I {} aws ec2 delete-snapshot --snapshot-id {}
2.2.3 使用文件系统快照工具(如ZFS)
# ZFS快照备份(假设数据集为tank/mongodb)
# 1. 创建快照
zfs snapshot tank/mongodb@backup_$(date +%Y%m%d)
# 2. 发送快照到备份服务器
zfs send tank/mongodb@backup_$(date +%Y%m%d) | ssh backup-server "zfs receive tank/mongodb_backup"
# 3. 删除旧快照(保留最近7天)
for snap in $(zfs list -t snapshot -o name | grep 'tank/mongodb@backup_' | head -n -7); do
zfs destroy $snap
done
2.3 物理备份的恢复
2.3.1 基本恢复步骤
# 1. 停止MongoDB服务
sudo systemctl stop mongod
# 2. 备份当前数据目录(如果需要)
sudo mv /data/db /data/db.bak
# 3. 复制备份文件到数据目录
sudo rsync -av /backup/mongodb/snapshot_20231201/ /data/db/
# 4. 修复数据库(可选,确保数据一致性)
sudo mongod --repair --dbpath /data/db
# 5. 启动MongoDB
sudo systemctl start mongod
# 6. 验证数据
mongosh --eval "db.adminCommand({listDatabases:1})"
2.3.2 跨平台/版本恢复注意事项
# 如果备份来自不同版本的MongoDB,可能需要使用mongodump/mongorestore进行迁移
# 1. 从物理备份启动临时实例
sudo mongod --dbpath /backup/mongodb/snapshot_20231201 --port 27018 --fork --logpath /tmp/mongod_restore.log
# 2. 使用mongodump导出数据
mongodump --host localhost --port 27018 --out /tmp/mongodb_dump
# 3. 停止临时实例
sudo mongod --dbpath /backup/mongodb/snapshot_20231201 --port 27018 --shutdown
# 4. 使用mongorestore导入到新版本
mongorestore --host localhost --port 27017 /tmp/mongodb_dump
三、副本集备份策略
3.1 副本集备份的最佳实践
在副本集环境中,备份策略需要特别考虑:
- 从Secondary节点备份:避免影响Primary节点的业务
- 读隔离(Read Concern):确保备份的数据一致性
- Oplog窗口:确保备份期间oplog不会被覆盖
3.2 从Secondary节点备份
# 1. 识别副本集中的Secondary节点
mongosh --eval "rs.status().members.forEach(m => print(m.name + ': ' + m.stateStr))"
# 2. 从Secondary节点执行备份(使用--readPreference=secondary)
mongodump --host secondary-host --port 27017 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--readPreference=secondary \
--out /backup/mongodb/replica_set_$(date +%Y%m%d)
3.3 使用副本集Oplog进行时间点恢复
# 1. 创建带Oplog的备份
mongodump --host secondary-host --port 27017 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--readPreference=secondary \
--oplog \
--out /backup/mongodb/replica_set_oplog_$(date +%Y%m%d)
# 2. 恢复时应用Oplog
mongorestore --host localhost --port 27017 \
--username admin --password "adminpass" --authenticationDatabase admin \
--oplogReplay \
--oplogLimit "2023-12-01T12:00:00Z" \
/backup/mongodb/replica_set_oplog_20231201
3.4 副本集备份脚本示例
#!/bin/bash
# 副本集备份脚本:replica_backup.sh
# 配置
BACKUP_BASE="/backup/mongodb"
DATE=$(date +%Y%m%d_%H%M%S)
REPLICA_NAME="rs0"
SECONDARY_HOST="secondary.example.com"
PORT=27017
USER="backupuser"
PASS="backupPass"
RETENTION_DAYS=7
# 创建备份目录
BACKUP_DIR="${BACKUP_BASE}/${REPLICA_NAME}_${DATE}"
mkdir -p "$BACKUP_DIR"
# 检查节点状态
NODE_STATUS=$(mongosh --host "$SECONDARY_HOST" --port "$PORT" --eval "
var status = rs.status();
var myMember = status.members.find(m => m.name.includes('$SECONDARY_HOST'));
if (!myMember) {
print('ERROR: Node not found in replica set');
quit(1);
}
if (myMember.stateStr !== 'SECONDARY') {
print('ERROR: Node is not SECONDARY. Current state: ' + myMember.stateStr);
quit(1);
}
print('OK: Node is SECONDARY');
" --quiet)
if [ $? -ne 0 ]; then
echo "Node status check failed: $NODE_STATUS"
exit 1
fi
# 执行备份
echo "Starting backup to $BACKUP_DIR"
mongodump --host "$SECONDARY_HOST" --port "$PORT" \
--username "$USER" --password "$PASS" --authenticationDatabase admin \
--readPreference=secondary \
--oplog \
--out "$BACKUP_DIR"
if [ $? -eq 0 ]; then
echo "Backup completed successfully"
# 压缩备份
echo "Compressing backup..."
tar -czf "${BACKUP_DIR}.tar.gz" -C "$BACKUP_BASE" "${REPLICA_NAME}_${DATE}"
rm -rf "$BACKUP_DIR"
# 清理旧备份
echo "Cleaning up old backups..."
find "$BACKUP_BASE" -name "${REPLICA_NAME}_*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup process completed: ${BACKUP_DIR}.tar.gz"
else
echo "Backup failed!"
exit 1
fi
四、分片集群备份策略
4.1 分片集群架构回顾
MongoDB分片集群包含以下组件:
- Config Servers:存储元数据
- Shards:实际数据分片
- Mongos:路由节点
备份分片集群需要协调所有组件,确保全局一致性。
4.2 分片集群备份步骤
4.2.1 备份Config Servers
# Config Servers必须作为副本集部署,从Secondary备份
mongodump --host config-server-secondary --port 27019 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--readPreference=secondary \
--oplog \
--out /backup/mongodb/config_$(date +%Y%m%d)
4.2.2 备份每个Shard
# 对每个分片副本集执行备份
for SHARD in shard1 shard2 shard3; do
# 找到分片的Secondary节点
SECONDARY=$(mongosh --host mongos --port 27017 --eval "
db.adminCommand({listShards:1}).shards.forEach(s => {
if (s._id === '$SHARD') {
// 连接到分片副本集获取Secondary
var rsStatus = new Mongo(s.host).getDB('admin').rs.status();
var secondary = rsStatus.members.find(m => m.stateStr === 'SECONDARY');
if (secondary) print(secondary.name);
}
});
" --quiet)
if [ -n "$SECONDARY" ]; then
mongodump --host "$SECONDARY" --port 27018 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--readPreference=secondary \
--oplog \
--out /backup/mongodb/${SHARD}_$(date +%Y%m%d)
fi
done
4.2.3 备份Mongos(可选)
Mongos本身不存储数据,但可以备份其配置:
# 备份Mongos配置(主要是路由规则)
mongodump --host mongos --port 27017 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--db config --collection chunks \
--out /backup/mongodb/mongos_config_$(date +%Y%m%d)
4.3 分片集群恢复
分片集群恢复需要按特定顺序进行:
# 1. 停止所有Mongos
sudo systemctl stop mongos
# 2. 恢复Config Servers
mongorestore --host config-server-primary --port 27019 \
--username admin --password "adminpass" --authenticationDatabase admin \
--oplogReplay \
/backup/mongodb/config_20231201
# 3. 恢复每个Shard
for SHARD in shard1 shard2 shard3; do
mongorestore --host ${SHARD}-primary --port 27018 \
--username admin --password "adminpass" --authenticationDatabase admin \
--oplogReplay \
/backup/mongodb/${SHARD}_20231201
done
# 4. 启动Mongos
sudo systemctl start mongos
# 5. 验证集群状态
mongosh --host mongos --port 27017 --eval "db.adminCommand({listShards:1})"
五、高级自动化备份方案
5.1 Percona Backup for MongoDB(PBM)
Percona Backup for MongoDB是MongoDB的开源备份管理工具,支持增量备份、时间点恢复和多云备份。
5.1.1 安装与配置
# 1. 安装PBM
wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb
sudo dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb
sudo apt-get update
sudo apt-get install percona-backup-mongodb
# 2. 配置PBM(在每个节点上)
sudo systemctl stop mongod
# 3. 配置PBM连接字符串
sudo tee /etc/pbm-agent.conf <<EOF
connection:
host: localhost
port: 27017
user: pbmuser
password: "pbmpass"
storage:
type: filesystem
filesystem:
path: /backup/pbm
EOF
# 4. 创建PBM用户(在MongoDB中)
mongosh --eval "
db.getSiblingDB('admin').createUser({
user: 'pbmuser',
pwd: 'pbmpass',
roles: [
{role: 'backup', db: 'admin'},
{role: 'clusterMonitor', db: 'admin'},
{role: 'readAnyDatabase', db: 'admin'},
{role: 'readWrite', db: 'local'}
]
});
"
# 5. 启动PBM Agent
sudo systemctl start pbm-agent
5.1.2 使用PBM进行备份
# 1. 配置备份存储(S3示例)
pbm config --file /dev/stdin <<EOF
storage:
type: s3
s3:
region: us-east-1
bucket: my-mongodb-backups
prefix: production
credentials:
access-key-id: AKIAIOSFODNN7EXAMPLE
secret-access-key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF
# 2. 执行备份
pbm backup --type=full --compression=gzip
# 3. 查看备份列表
pbm list
# 4. 恢复备份
pbm restore 2023-12-01T12:00:00Z
# 5. 时间点恢复(PITR)
pbm config --file /dev/stdin <<EOF
pitr:
enabled: true
oplogSpanMin: 10
EOF
# 6. 执行PITR恢复
pbm restore --time="2023-12-01T12:30:00"
5.2 自动化脚本与Cron任务
5.2.1 完整的自动化备份脚本
#!/bin/bash
# MongoDB自动化备份脚本:mongo_backup_auto.sh
# 配置
BACKUP_BASE="/backup/mongodb"
DATE=$(date +%Y%m%d_%H%M%S)
HOST="localhost"
PORT=27017
USER="backupuser"
PASS="backupPass"
AUTH_DB="admin"
RETENTION_DAYS=7
COMPRESS=true
LOG_FILE="/var/log/mongodb_backup.log"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 错误处理
error_exit() {
log "ERROR: $1"
exit 1
}
# 检查MongoDB连接
check_mongodb() {
log "Checking MongoDB connection..."
if ! mongosh --host "$HOST" --port "$PORT" --username "$USER" --password "$PASS" --authenticationDatabase "$AUTH_DB" --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; then
error_exit "Cannot connect to MongoDB"
fi
log "MongoDB connection OK"
}
# 检查磁盘空间
check_disk_space() {
log "Checking disk space..."
AVAILABLE=$(df "$BACKUP_BASE" | awk 'NR==2 {print $4}')
# 估算需要的空间(这里简化,实际应根据数据大小调整)
REQUIRED=$((10 * 1024 * 1024)) # 10GB
if [ "$AVAILABLE" -lt "$REQUIRED" ]; then
error_exit "Insufficient disk space. Available: ${AVAILABLE}KB, Required: ${REQUIRED}KB"
fi
log "Disk space OK"
}
# 执行备份
perform_backup() {
local backup_dir="${BACKUP_BASE}/${DATE}"
mkdir -p "$backup_dir"
log "Starting backup to $backup_dir"
# 获取副本集状态
REPLICA_STATUS=$(mongosh --host "$HOST" --port "$PORT" --username "$USER" --password "$PASS" --authenticationDatabase "$AUTH_DB" --eval "
try {
var status = db.adminCommand({replSetGetStatus:1});
if (status.ok) {
var primary = status.members.find(m => m.stateStr === 'PRIMARY');
var secondary = status.members.find(m => m.stateStr === 'SECONDARY');
if (secondary) {
print('SECONDARY:' + secondary.name);
} else if (primary) {
print('PRIMARY:' + primary.name);
} else {
print('STANDALONE:' + '$HOST');
}
} else {
print('STANDALONE:' + '$HOST');
}
} catch (e) {
print('STANDALONE:' + '$HOST');
}
" --quiet)
# 确定备份源
if [[ "$REPLICA_STATUS" =~ ^SECONDARY: ]]; then
BACKUP_HOST="${REPLICA_STATUS#SECONDARY:}"
READ_PREF="--readPreference=secondary"
log "Using SECONDARY node: $BACKUP_HOST"
elif [[ "$REPLICA_STATUS" =~ ^PRIMARY: ]]; then
BACKUP_HOST="${REPLICA_STATUS#PRIMARY:}"
READ_PREF=""
log "Using PRIMARY node: $BACKUP_HOST (no secondary available)"
else
BACKUP_HOST="$HOST"
READ_PREF=""
log "Using standalone node: $BACKUP_HOST"
fi
# 执行备份
if mongodump --host "$BACKUP_HOST" --port "$PORT" \
--username "$USER" --password "$PASS" --authenticationDatabase "$AUTH_DB" \
$READ_PREF \
--oplog \
--out "$backup_dir" 2>> "$LOG_FILE"; then
log "Backup completed successfully"
else
error_exit "Backup failed"
fi
# 压缩
if [ "$COMPRESS" = true ]; then
log "Compressing backup..."
if tar -czf "${backup_dir}.tar.gz" -C "$BACKUP_BASE" "$DATE" 2>> "$LOG_FILE"; then
rm -rf "$backup_dir"
log "Compression completed: ${backup_dir}.tar.gz"
BACKUP_FILE="${backup_dir}.tar.gz"
else
error_exit "Compression failed"
fi
else
BACKUP_FILE="$backup_dir"
fi
}
# 清理旧备份
cleanup_old_backups() {
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$BACKUP_BASE" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete 2>> "$LOG_FILE"
find "$BACKUP_BASE" -mindepth 1 -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} + 2>> "$LOG_FILE"
log "Cleanup completed"
}
# 发送通知(可选)
send_notification() {
local status=$1
local message=$2
# 邮件通知(需要配置mailx或sendmail)
if command -v mailx &> /dev/null; then
echo "$message" | mailx -s "MongoDB Backup $status: $DATE" admin@example.com
fi
# Slack通知(需要配置SLACK_WEBHOOK)
if [ -n "$SLACK_WEBHOOK" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"MongoDB Backup $status: $DATE\n$message\"}" \
"$SLACK_WEBHOOK"
fi
}
# 主函数
main() {
log "=== MongoDB Backup Started ==="
check_mongodb
check_disk_space
perform_backup
cleanup_old_backups
log "=== MongoDB Backup Completed Successfully ==="
send_notification "SUCCESS" "Backup completed: $BACKUP_FILE"
}
# 错误处理
trap 'error_exit "Script interrupted"' INT TERM
# 执行主函数
main "$@"
5.2.2 Cron任务配置
# 编辑crontab
crontab -e
# 添加以下行(每天凌晨2点执行)
0 2 * * * /usr/local/bin/mongo_backup_auto.sh
# 每周日执行完整备份,其他时间增量(使用PBM)
0 2 * * 0 /usr/local/bin/pbm_backup_full.sh
0 2 * * 1-6 /usr/local/bin/pbm_backup_incremental.sh
# 每小时检查备份状态
0 * * * * /usr/local/bin/check_backup_status.sh
5.2.3 备份监控与告警脚本
#!/bin/bash
# 备份监控脚本:check_backup_status.sh
BACKUP_BASE="/backup/mongodb"
LOG_FILE="/var/log/mongodb_backup.log"
ALERT_EMAIL="admin@example.com"
# 检查最近24小时是否有成功备份
find "$BACKUP_BASE" -name "*.tar.gz" -mtime -1 | grep -q .
if [ $? -ne 0 ]; then
echo "CRITICAL: No successful backup found in last 24 hours" | mailx -s "MongoDB Backup Alert" "$ALERT_EMAIL"
exit 1
fi
# 检查备份文件大小(异常告警)
LATEST_BACKUP=$(find "$BACKUP_BASE" -name "*.tar.gz" -mtime -1 | head -1)
if [ -n "$LATEST_BACKUP" ]; then
SIZE=$(stat -c%s "$LATEST_BACKUP")
# 如果备份文件小于100MB,可能有问题
if [ "$SIZE" -lt 104857600 ]; then
echo "WARNING: Latest backup size is suspiciously small: $SIZE bytes" | mailx -s "MongoDB Backup Warning" "$ALERT_EMAIL"
fi
fi
# 检查日志中的错误
if grep -q "ERROR" "$LOG_FILE" | tail -100; then
echo "ERROR found in backup log" | mailx -s "MongoDB Backup Error" "$ALERT_EMAIL"
exit 1
fi
echo "Backup status OK"
exit 0
六、备份验证与恢复测试
6.1 备份验证的重要性
备份不测试等于没有备份。定期验证备份的完整性和可恢复性至关重要。
6.2 自动化验证脚本
#!/bin/bash
# 备份验证脚本:verify_backup.sh
BACKUP_BASE="/backup/mongodb"
VERIFY_DIR="/tmp/mongodb_verify"
MONGOD_PORT=27020
# 清理旧验证环境
cleanup() {
if [ -d "$VERIFY_DIR" ]; then
sudo rm -rf "$VERIFY_DIR"
fi
if ps -p $MONGOD_PID > /dev/null 2>&1; then
sudo kill $MONGOD_PID 2>/dev/null
sleep 2
sudo pkill -f "mongod.*port.*$MONGOD_PORT"
fi
}
trap cleanup EXIT
# 选择最新备份
LATEST_BACKUP=$(find "$BACKUP_BASE" -name "*.tar.gz" -mtime -1 | sort | tail -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "No backup found to verify"
exit 1
fi
echo "Verifying backup: $LATEST_BACKUP"
# 创建验证目录
mkdir -p "$VERIFY_DIR"
mkdir -p "$VERIFY_DIR/db"
# 解压备份
echo "Extracting backup..."
if ! tar -xzf "$LATEST_BACKUP" -C "$VERIFY_DIR" 2>/dev/null; then
echo "FAILED: Cannot extract backup"
exit 1
fi
# 找到解压后的BSON文件目录
EXTRACTED_DIR=$(find "$VERIFY_DIR" -name "*.bson" -type f | head -1 | xargs dirname)
if [ -z "$EXTRACTED_DIR" ]; then
echo "FAILED: No BSON files found in backup"
exit 1
fi
# 启动临时MongoDB实例
echo "Starting temporary MongoDB instance on port $MONGOD_PORT..."
sudo mongod --dbpath "$VERIFY_DIR/db" --port $MONGOD_PORT --fork --logpath "$VERIFY_DIR/mongod.log" --bind_ip 127.0.0.1
MONGOD_PID=$(cat "$VERIFY_DIR/db/mongod.lock" 2>/dev/null)
# 等待MongoDB启动
sleep 5
# 恢复数据
echo "Restoring data to temporary instance..."
if ! mongorestore --host 127.0.0.1 --port $MONGOD_PORT "$EXTRACTED_DIR" 2>/dev/null; then
echo "FAILED: Restore failed"
exit 1
fi
# 验证数据
echo "Verifying data integrity..."
VERIFICATION_RESULT=$(mongosh --host 127.0.0.1 --port $MONGOD_PORT --eval "
var errors = [];
// 检查数据库列表
var dbs = db.adminCommand({listDatabases:1}).databases;
if (dbs.length === 0) errors.push('No databases found');
// 检查每个数据库的集合
dbs.forEach(function(db) {
if (db.name === 'admin' || db.name === 'local' || db.name === 'config') return;
var colls = db.getSiblingDB(db.name).getCollectionNames();
colls.forEach(function(coll) {
if (coll.indexOf('system.') === -1) {
var count = db.getSiblingDB(db.name)[coll].countDocuments();
if (count === 0) {
errors.push('Empty collection: ' + db.name + '.' + coll);
}
}
});
});
if (errors.length > 0) {
print('VERIFICATION FAILED:');
errors.forEach(function(err) { print(' - ' + err); });
quit(1);
} else {
print('VERIFICATION SUCCESS: All databases and collections have data');
quit(0);
}
" --quiet)
if [ $? -eq 0 ]; then
echo "Backup verification PASSED"
exit 0
else
echo "$VERIFICATION_RESULT"
exit 1
fi
6.3 定期恢复测试计划
#!/bin/bash
# 每月恢复测试脚本:monthly_restore_test.sh
# 配置
BACKUP_BASE="/backup/mongodb"
TEST_ENV="/tmp/mongodb_restore_test"
TEST_PORT=27021
TEST_DB="restore_test_db"
# 创建测试环境
mkdir -p "$TEST_ENV/db"
mkdir -p "$TEST_ENV/restore"
# 选择一个随机备份(至少30天前)
BACKUP=$(find "$BACKUP_BASE" -name "*.tar.gz" -mtime +30 | shuf -n 1)
if [ -z "$BACKUP" ]; then
echo "No suitable backup found for monthly test"
exit 1
fi
echo "Monthly restore test using: $BACKUP"
# 解压备份
tar -xzf "$BACKUP" -C "$TEST_ENV/restore"
# 找到BSON文件
BSON_DIR=$(find "$TEST_ENV/restore" -name "*.bson" -type f | head -1 | xargs dirname)
# 启动临时实例
sudo mongod --dbpath "$TEST_ENV/db" --port $TEST_PORT --fork --logpath "$TEST_ENV/mongod.log" --bind_ip 127.0.0.1
# 恢复到测试数据库(重命名)
mongorestore --host 127.0.0.1 --port $TEST_PORT \
--nsFrom "*.*" --nsFrom "*.*" --nsTo "${TEST_DB}.*" \
"$BSON_DIR"
# 运行数据完整性检查
mongosh --host 127.0.0.1 --port $TEST_PORT --eval "
var testDB = db.getSiblingDB('$TEST_DB');
var collections = testDB.getCollectionNames();
var totalDocs = 0;
collections.forEach(function(coll) {
if (coll.indexOf('system.') === -1) {
var count = testDB[coll].countDocuments();
totalDocs += count;
print('Collection ' + coll + ': ' + count + ' documents');
}
});
print('Total documents restored: ' + totalDocs);
// 执行一些查询测试
if (totalDocs > 0) {
var sampleColl = collections.find(c => c.indexOf('system.') === -1);
if (sampleColl) {
var sample = testDB[sampleColl].findOne();
print('Sample document: ' + JSON.stringify(sample, null, 2));
}
}
"
# 清理
sudo mongod --dbpath "$TEST_ENV/db" --port $TEST_PORT --shutdown
sudo rm -rf "$TEST_ENV"
echo "Monthly restore test completed successfully"
七、备份安全与合规
7.1 备份加密
7.1.1 使用GPG加密备份
#!/bin/bash
# 加密备份脚本:encrypt_backup.sh
BACKUP_FILE="$1"
GPG_RECIPIENT="backup-key@example.com"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup_file>"
exit 1
fi
# 生成对称加密密钥(更安全)
SYMMETRIC_KEY=$(openssl rand -base64 32)
echo "$SYMMETRIC_KEY" | gpg --batch --yes --passphrase-fd 0 --symmetric --cipher-algo AES256 "${BACKUP_FILE}.key"
# 使用对称密钥加密备份
gpg --batch --yes --passphrase-file "${BACKUP_FILE}.key" --symmetric --cipher-algo AES256 --output "${BACKUP_FILE}.gpg" "$BACKUP_FILE"
# 清理原始文件(可选)
# rm "$BACKUP_FILE"
echo "Backup encrypted: ${BACKUP_FILE}.gpg"
echo "Key file: ${BACKUP_FILE}.key"
echo "Store the key file securely!"
7.1.2 使用MongoDB加密备份(WiredTiger加密)
# 如果MongoDB使用WiredTiger加密,物理备份自动加密
# 但逻辑备份需要额外处理
# 在mongodump时使用加密传输
mongodump --host localhost --port 27017 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--ssl --sslPEMKeyFile /path/to/client.pem \
--out /backup/mongodb/encrypted_$(date +%Y%m%d)
7.2 备份访问控制
# 创建专用备份用户(最小权限原则)
mongosh --eval "
db.getSiblingDB('admin').createUser({
user: 'backupuser',
pwd: 'backupPass',
roles: [
{role: 'backup', db: 'admin'},
{role: 'clusterMonitor', db: 'admin'},
{role: 'readAnyDatabase', db: 'admin'},
{role: 'readWrite', db: 'local'}
]
});
"
# 限制备份用户只能从特定IP访问
# 在mongod.conf中配置:
# security:
# authorization: enabled
# authorization: enabled
# clusterAuthMode: x509
7.3 备份审计
# 记录所有备份操作到系统日志
logger -t mongodb_backup "Starting backup of $DATABASE"
# 在备份脚本中添加审计日志
log_backup_action() {
local action=$1
local status=$2
local details=$3
# 写入本地日志
echo "$(date -Iseconds) $action $status $details" >> /var/log/mongodb_backup_audit.log
# 发送到远程日志服务器
logger -t mongodb_backup -p local0.info "$action $status $details"
# 可选:发送到SIEM系统
# curl -X POST https://siem.example.com/api/logs \
# -H "Authorization: Bearer $SIEM_TOKEN" \
# -d "{\"timestamp\":\"$(date -Iseconds)\",\"action\":\"$action\",\"status\":\"$status\",\"details\":\"$details\"}"
}
八、备份策略设计与最佳实践
8.1 备份策略设计原则
8.1.1 3-2-1备份规则
- 3:至少3份数据副本
- 2:存储在2种不同介质上
- 1:至少1份异地备份
8.1.2 RPO与RTO定义
- RPO(恢复点目标):可接受的数据丢失量(如1小时)
- RTO(恢复时间目标):恢复业务所需时间(如4小时)
8.2 不同场景的备份策略示例
8.2.1 开发/测试环境
# 策略:每日全量备份,保留7天
# 使用mongodump,无需oplog
0 2 * * * mongodump --host localhost --port 27017 --out /backup/dev/$(date +%Y%m%d) && find /backup/dev -mtime +7 -delete
8.2.2 生产环境(单机/副本集)
# 策略:
# - 每日全量备份(使用PBM)
# - 每小时增量备份(PITR)
# - 保留30天全量,7天增量
# - 异地备份到S3
# PBM配置
pbm config --file /dev/stdin <<EOF
storage:
type: s3
s3:
region: us-east-1
bucket: prod-mongodb-backups
prefix: daily
pitr:
enabled: true
oplogSpanMin: 60
EOF
# 每日全量
0 2 * * * pbm backup --type=full --compression=gzip
# 每月1号执行验证
0 3 1 * * /usr/local/bin/verify_backup.sh
8.2.3 金融/医疗行业(合规要求)
# 策略:
# - 每日全量备份
# - 实时WAL归档(Percona Server)
# - 备份加密
# - 异地多副本存储
# - 保留7年
# 配置WAL归档(Percona Server for MongoDB)
# mongod.conf
storage:
wiredTiger:
engineConfig:
journalCompressor: snappy
syncPeriodSecs: 60
# 启用归档
sudo mkdir -p /var/lib/mongodb/archive
sudo chown -R mongodb:mongodb /var/lib/mongodb/archive
# 配置归档脚本(每15分钟)
*/15 * * * * /usr/local/bin/archive_wal.sh
# 备份加密
0 2 * * * /usr/local/bin/mongo_backup_auto.sh && /usr/local/bin/encrypt_backup.sh /backup/mongodb/latest.tar.gz
8.3 备份监控与告警体系
# 综合监控脚本:backup_monitor.sh
#!/bin/bash
# 配置
BACKUP_BASE="/backup/mongodb"
LOG_FILE="/var/log/mongodb_backup_monitor.log"
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
PAGERDUTY_KEY="your-pagerduty-key"
# 检查函数
check_backup_age() {
local max_age_hours=25
local latest=$(find "$BACKUP_BASE" -name "*.tar.gz" -type f -mmin -$((max_age_hours * 60)) 2>/dev/null)
if [ -z "$latest" ]; then
send_alert "CRITICAL" "No backup found in last $max_age_hours hours"
return 1
fi
local age=$(stat -c %Y "$latest")
local now=$(date +%s)
local age_hours=$(( (now - age) / 3600 ))
if [ $age_hours -gt 24 ]; then
send_alert "WARNING" "Latest backup is $age_hours hours old"
fi
return 0
}
check_backup_size() {
local latest=$(find "$BACKUP_BASE" -name "*.tar.gz" -type f -printf "%s\n" | head -1)
if [ -n "$latest" ] && [ "$latest" -lt 104857600 ]; then
send_alert "WARNING" "Backup size too small: $latest bytes"
fi
}
check_restore_test() {
local last_test=$(stat -c %Y /tmp/mongodb_verify 2>/dev/null)
local now=$(date +%s)
local days=$(( (now - last_test) / 86400 ))
if [ $days -gt 30 ]; then
send_alert "WARNING" "Restore test not run in $days days"
fi
}
# 告警函数
send_alert() {
local level=$1
local message=$2
local timestamp=$(date -Iseconds)
# 日志
echo "[$timestamp] $level: $message" >> "$LOG_FILE"
# Slack
if [ -n "$SLACK_WEBHOOK" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"MongoDB Backup $level: $message\"}" \
"$SLACK_WEBHOOK" > /dev/null 2>&1
fi
# PagerDuty(仅CRITICAL)
if [ "$level" = "CRITICAL" ] && [ -n "$PAGERDUTY_KEY" ]; then
curl -X POST -H "Content-Type: application/json" \
-d "{\"routing_key\":\"$PAGERDUTY_KEY\",\"event_action\":\"trigger\",\"payload\":{\"summary\":\"$message\",\"severity\":\"critical\"}}" \
https://events.pagerduty.com/v2/enqueue > /dev/null 2>&1
fi
# 邮件
if [ "$level" = "CRITICAL" ]; then
echo "$message" | mailx -s "MongoDB Backup $level" admin@example.com
fi
}
# 主监控流程
main() {
local errors=0
check_backup_age || ((errors++))
check_backup_size || ((errors++))
check_restore_test || ((errors++))
if [ $errors -eq 0 ]; then
echo "All backup checks passed"
exit 0
else
echo "$errors backup check(s) failed"
exit 1
fi
}
main "$@"
九、常见问题与解决方案
9.1 备份失败常见原因
9.1.1 网络问题
# 诊断网络连接
mongosh --host backup-host --eval "db.adminCommand('ping')" --quiet
# 使用带超时的备份
mongodump --host backup-host --port 27017 \
--username backupuser --password "backupPass" --authenticationDatabase admin \
--timeoutMS=30000 \
--out /backup/mongodb/$(date +%Y%m%d)
9.1.2 磁盘空间不足
# 预估备份大小
mongosh --eval "
var totalSize = 0;
db.adminCommand({listDatabases:1}).databases.forEach(function(db){
var size = db.getSiblingDB(db.name).stats().dataSize;
totalSize += size;
});
print('Estimated backup size: ' + (totalSize / 1024 / 1024 / 1024).toFixed(2) + ' GB');
"
# 清理磁盘空间
# 1. 删除旧备份
find /backup/mongodb -name "*.tar.gz" -mtime +30 -delete
# 2. 清理MongoDB日志
sudo journalctl --vacuum-time=7d
# 3. 压缩旧日志
find /var/log/mongodb -name "*.log" -mtime +7 -exec gzip {} \;
9.1.3 权限不足
# 检查备份用户权限
mongosh --eval "
db.getSiblingDB('admin').getUser('backupuser')
"
# 重新授权
mongosh --eval "
db.getSiblingDB('admin').grantRolesToUser('backupuser', [
{role: 'backup', db: 'admin'},
{role: 'clusterMonitor', db: 'admin'},
{role: 'readAnyDatabase', db: 'admin'}
]);
"
9.2 恢复失败常见原因
9.2.1 版本不兼容
# 检查备份的MongoDB版本
mongodump --version
# 如果版本差异大,使用中间版本过渡
# 例如:从3.6备份恢复到5.0
# 1. 使用4.0版本临时恢复
# 2. 从4.0导出为JSON
# 3. 导入到5.0
9.2.2 索引损坏
# 恢复时跳过索引,后续重建
mongorestore --noIndexRestore /backup/mongodb/20231201
# 重建索引
mongosh --eval "
db.getCollectionNames().forEach(function(coll){
if (coll.indexOf('system.') === -1) {
print('Reindexing: ' + coll);
db[coll].reIndex();
}
});
"
9.2.3 数据冲突(分片集群)
# 在分片集群恢复时,如果数据已存在,需要使用--drop
mongorestore --drop --host mongos --port 27017 /backup/mongodb/20231201
# 或者使用--mode=merge(PBM)
pbm restore --mode=merge 2023-12-01T12:00:00Z
十、总结与建议
10.1 备份策略选择指南
| 场景 | 推荐工具 | 备份频率 | 保留周期 | 恢复时间 |
|---|---|---|---|---|
| 开发测试 | mongodump | 每日 | 7天 | 小时级 |
| 生产单机 | PBM + 文件系统快照 | 每日全量 + 每小时增量 | 30天 + 7天 | 分钟级 |
| 生产副本集 | PBM + 从Secondary备份 | 每日全量 + 每小时PITR | 30天 + 7天 | 分钟级 |
| 生产分片集群 | PBM + 分片级备份 | 每日全量 + 每小时PITR | 30天 + 7天 | 小时级 |
| 合规要求 | PBM + WAL归档 + 加密 | 每日全量 + 实时归档 | 7年 | 小时级 |
10.2 关键成功因素
- 自动化:所有备份和恢复操作应自动化,减少人为错误
- 监控:建立完善的监控和告警体系
- 测试:定期进行恢复测试,确保备份可用
- 文档:详细记录备份策略和恢复步骤
- 安全:保护备份数据,防止未授权访问
- 异地:确保至少一份备份存储在异地
10.3 持续改进
- 定期审查:每季度审查备份策略的有效性
- 容量规划:根据数据增长调整备份存储
- 技术更新:关注MongoDB新版本的备份特性
- 演练:每半年进行一次灾难恢复演练
通过实施本文介绍的备份策略,您可以有效降低数据丢失风险,确保在发生灾难时能够快速恢复业务。记住,备份的价值只有在恢复成功时才能体现,因此请务必重视备份验证和恢复测试。
