引言:为什么MongoDB备份至关重要
在现代应用架构中,MongoDB作为领先的NoSQL数据库,承载着大量关键业务数据。然而,数据丢失的风险始终存在——无论是硬件故障、人为错误、恶意攻击还是自然灾害。一个完善的备份策略不仅是数据安全的最后防线,更是业务连续性的核心保障。
MongoDB的备份不同于传统关系型数据库,其灵活的文档结构、分片集群架构和动态模式带来了独特的挑战。本文将深入探讨MongoDB备份的核心原理、实用工具和最佳实践,帮助您制定高效的备份计划,有效应对数据丢失风险与恢复挑战。
MongoDB备份的核心挑战
1. 数据一致性与完整性
MongoDB支持灵活的读写操作,如何在备份过程中确保数据一致性是首要挑战。特别是对于写入密集型应用,简单的文件复制可能导致备份数据不一致。
2. 分片集群的复杂性
MongoDB分片集群包含多个组件:配置服务器、分片副本集和路由节点。备份必须协调所有这些组件,确保跨分片的数据一致性。
3. 存储引擎差异
MongoDB支持多种存储引擎(WiredTiger、In-Memory、MMAPv1),每种引擎的备份机制和性能特征各不相同。
4. 大数据量处理
随着业务增长,单个集合可能达到TB级别,如何在有限的时间窗口内完成备份成为难题。
MongoDB备份工具详解
mongodump:逻辑备份工具
mongodump是MongoDB官方提供的逻辑备份工具,它通过查询MongoDB服务器导出BSON格式的数据。
基本用法:
# 备份单个数据库
mongodump --host localhost --port 27017 --db myapp --out /backup/mongodb/$(date +%Y%m%d)
# 备份所有数据库
mongodump --host localhost --port 27017 --out /backup/mongodb/full_$(date +%Y%m%d)
# 使用认证备份
mongodump --host localhost --port 27017 --username backupuser --password "securepass" --authenticationDatabase admin --db myapp --out /backup/mongodb/
# 压缩备份(使用gzip)
mongodump --host localhost --port 27017 --db myapp --gzip --out /backup/mongodb/compressed_$(date +%Y%m%d)
高级选项:
# 排除特定集合
mongodump --host localhost --port 27017 --db myapp --excludeCollection=logs --excludeCollection=temp_data --out /backup/mongodb/
# 查询条件备份(仅备份符合条件的文档)
mongodump --host localhost --port 27017 --db myapp --collection=users --query='{ "created_at": { "$gte": { "$date": "2024-01-01T00:00:00Z" } } }' --out /backup/mongodb/
# 备份到标准输出并压缩
mongodump --host localhost --port 27017 --db myapp --archive=/backup/mongodb/myapp.archive --gzip
mongodump的工作原理:
- 建立到MongoDB的连接并执行
listDatabases命令(如果备份所有数据库) - 对每个要备份的数据库执行
listCollections命令 - 对每个集合,使用查询游标读取所有文档
- 将文档序列化为BSON格式并写入输出文件
- 同时导出集合索引定义(存储在system.indexes集合中)
优点:
- 跨平台兼容性好
- 支持选择性备份(单个数据库、集合或查询子集)
- 支持压缩和流式处理
- 可以备份远程服务器
缺点:
- 速度较慢(需要遍历所有文档)
- 备份过程中可能影响数据库性能
- 恢复时需要重建索引(耗时较长)
- 不保证时间点一致性(除非配合oplog)
mongorestore:逻辑恢复工具
mongorestore用于从mongodump生成的BSON文件中恢复数据。
基本用法:
# 恢复单个数据库
mongorestore --host localhost --port 27017 --db myapp /backup/mongodb/20240101/myapp/
# 恢复所有数据库
mongorestore --host localhost --port 27017 /backup/mongodb/full_20240101/
# 使用认证恢复
mongorestore --host localhost --port 27017 --username restoreuser --password "securepass" --authenticationDatabase admin --db myapp /backup/mongodb/myapp/
# 恢复时压缩
mongorestore --host localhost --port 27017 --gzip --archive=/backup/mongodb/myapp.archive --db myapp
高级选项:
# 恢复时删除原有数据(危险操作,需谨慎)
mongorestore --host localhost --port 27017 --drop --db myapp /backup/mongodb/myapp/
# 恢复到不同数据库名
mongorestore --host localhost --port 27017 --db newapp --nsFrom 'myapp.*' --nsTo 'newapp.*' /backup/mongodb/myapp/
# 并行恢复(提高速度)
mongorestore --host localhost --port 27017 --numInsertionWorkersPerCollection=4 --db myapp /backup/mongodb/myapp/
# 恢复索引(默认会恢复索引,但可以跳过)
mongorestore --host localhost --port 27017 --noIndexRestore --db myapp /backup/mongodb/myapp/
mongorestore的工作原理:
- 读取BSON文件并解析为MongoDB文档
- 将文档插入到目标集合中
- 如果目标集合已存在,默认会合并数据(相同_id的文档会被覆盖)
- 恢复索引定义(除非指定–noIndexRestore)
- 恢复后需要重建索引(如果索引不存在)
优点:
- 灵活性高,支持选择性恢复
- 支持压缩和流式处理
- 可以恢复到不同数据库/集合
- 支持合并模式
缺点:
- 恢复速度较慢(特别是索引重建)
- 需要足够的磁盘空间(临时文件)
- 大量数据恢复时可能影响性能
文件系统快照备份
文件系统快照备份是基于存储级别的物理备份,适用于WiredTiger存储引擎。
LVM快照备份示例(Linux):
# 1. 锁定数据库(确保一致性)
mongod --dbpath /data/db --repair --repairpath /data/db/repair
# 2. 创建LVM快照(假设MongoDB数据目录在/data/db)
lvcreate --size 10G --snapshot --name mongodb-snap /dev/vg0/mongodb-data
# 3. 挂载快照
mount /dev/vg0/mongodb-snap /mnt/mongodb-snap
# 4. 复制数据(使用tar或rsync)
tar -czf /backup/mongodb/snapshot_$(date +%Y%m%d).tar.gz -C /mnt/mongodb-snap .
# 5. 卸载并删除快照
umount /mnt/mongodb-snap
lvremove -f /dev/vg0/mongodb-snap
EC2/EBS快照备份示例:
# 1. 冻结文件系统(可选,取决于文件系统类型)
fsfreeze -f /data/db
# 2. 创建EBS快照
aws ec2 create-snapshot --volume-id vol-0abcd1234 --description "MongoDB Daily Backup"
# 3. 解冻文件系统
fsfreeze -u /data/db
# 4. 等待快照完成
aws ec2 wait snapshot-completed --snapshot-ids snap-0abcd1234
文件系统快照的工作原理:
- 存储系统在特定时间点创建数据卷的快照
- 快照是只读的,包含该时间点的所有数据块
- 可以挂载快照并复制数据,或直接使用快照恢复
优点:
- 速度极快(通常几分钟内完成)
- 对数据库性能影响极小
- 备份文件是完整的数据文件副本
- 恢复速度极快(直接使用数据文件)
缺点:
- 需要特定的存储基础设施(LVM、ZFS、EBS等)
- 快照可能占用额外存储空间
- 跨平台兼容性差
- 无法选择性备份单个数据库/集合
MongoDB Atlas在线备份
MongoDB Atlas是MongoDB官方托管服务,提供企业级备份解决方案。
Atlas备份特性:
- 连续备份:基于oplog实现时间点恢复(PITR)
- 快照备份:每日全量快照
- 全球部署:跨区域备份
- 加密存储:静态数据加密
Atlas备份配置示例:
// 通过Atlas API配置备份
const axios = require('axios');
const config = {
url: 'https://cloud.mongodb.com/api/atlas/v1.0/groups/{groupId}/clusters/{clusterName}/backup/snapshots',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from('publicKey:privateKey').toString('base64')
},
data: {
"snapshotType": "scheduled",
"frequencyType": "daily",
"retentionDays": 7
}
};
axios(config).then(response => {
console.log('Backup configuration updated:', response.data);
}).catch(error => {
console.error('Error:', error.response.data);
});
Atlas恢复操作:
// 通过Atlas API恢复集群
const axios = require('axios');
const config = {
url: 'https://cloud.mongodb.com/api/atlas/v1.0/groups/{groupId}/clusters/{clusterName}/restoreJobs',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from('publicKey:privateKey').toString('base64')
},
data: {
"snapshotId": "63f8c2b5f7a8b9c7d8e9f0a1",
"targetClusterName": "myapp-restored",
"targetGroupId": "{groupId}"
}
};
axios(config).then(response => {
console.log('Restore job created:', response.data);
}).catch(error => {
console.error('Error:', error.response.data);
});
制定高效备份计划
1. 备份策略设计原则
3-2-1备份法则:
- 3:至少保留3份数据副本(原始数据 + 2份备份)
- 2:使用2种不同的存储介质(例如:本地磁盘 + 云存储)
- 1:至少1份备份存储在异地(防灾)
RPO(恢复点目标)与RTO(恢复时间目标):
- RPO:可容忍的数据丢失量(例如:15分钟)
- RTO:从故障到恢复服务所需的时间(例如:2小时)
备份频率与保留策略:
# 示例:电商数据库备份策略
backups:
- name: "实时oplog备份"
type: "oplog"
frequency: "continuous"
retention: "7天"
purpose: "时间点恢复"
- name: "每日增量备份"
type: "mongodump"
frequency: "每天凌晨2:00"
retention: "30天"
purpose: "快速恢复"
- name: "每周全量备份"
type: "文件系统快照"
frequency: "每周日02:00"
retention: "90天"
purpose: "灾难恢复"
- name: "每月归档备份"
type: "mongodump"
frequency: "每月1日02:00"
retention: "1年"
purpose: "合规性"
2. 分片集群备份策略
分片集群备份需要协调多个组件,确保全局一致性。
分片集群备份步骤:
#!/bin/bash
# MongoDB分片集群备份脚本
# 配置
BACKUP_DIR="/backup/mongodb/sharded/$(date +%Y%m%d_%H%M%S)"
MONGOS_HOST="mongos.example.com:27017"
CONFIG_HOST="config.example.com:27018"
SHARDS=("shard1.example.com:27018" "shard2.example.com:27018" "shard3.example.com:27018")
RETENTION_DAYS=30
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 1. 备份配置服务器(必须首先锁定)
echo "备份配置服务器..."
mongodump --host "$CONFIG_HOST" --oplog --out "$BACKUP_DIR/config" --gzip
# 2. 备份每个分片
for shard in "${SHARDS[@]}"; do
echo "备份分片: $shard"
mongodump --host "$shard" --oplog --out "$BACKUP_DIR/shards/${shard//:/_}" --gzip
done
# 3. 备份mongos元数据(可选,但推荐)
echo "备份mongos元数据..."
mongodump --host "$MONGOS_HOST" --db config --out "$BACKUP_DIR/mongos" --gzip
# 4. 创建备份元数据文件
cat > "$BACKUP_DIR/backup_metadata.json" <<EOF
{
"timestamp": "$(date -Iseconds)",
"config_server": "$CONFIG_HOST",
"shards": [$(printf '"%s",' "${SHARDS[@]}" | sed 's/,$//')],
"mongos": "$MONGOS_HOST",
"oplog": true
}
EOF
# 5. 上传到云存储(例如S3)
aws s3 sync "$BACKUP_DIR" "s3://my-mongodb-backups/sharded/$(basename $BACKUP_DIR)/"
# 6. 清理旧备份
find /backup/mongodb/sharded/ -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;
aws s3 ls "s3://my-mongodb-backups/sharded/" | awk '{print $2}' | while read dir; do
# 删除超过保留期的S3备份
aws s3 rm "s3://my-mongodb-backups/sharded/$dir" --recursive
done
echo "备份完成: $BACKUP_DIR"
分片集群恢复步骤:
#!/bin/bash
# MongoDB分片集群恢复脚本
BACKUP_DIR="/backup/mongodb/sharded/20240101_020000"
MONGOS_HOST="mongos.example.com:27017"
CONFIG_HOST="config.example.com:27018"
SHARDS=("shard1.example.com:27018" "shard2.example.com:27018" "shard3.example.com:27018")
# 1. 恢复配置服务器
echo "恢复配置服务器..."
mongorestore --host "$CONFIG_HOST" --oplogReplay --gzip --archive="$BACKUP_DIR/config/oplog.bson"
# 2. 恢复每个分片
for shard in "${SHARDS[@]}"; do
echo "恢复分片: $shard"
mongorestore --host "$shard" --oplogReplay --gzip --archive="$BACKUP_DIR/shards/${shard//:/_}/oplog.bson"
done
# 3. 重启mongos(如果需要)
echo "请手动重启mongos服务以应用新的元数据"
3. 自动化备份脚本
完整的自动化备份脚本(支持单实例和副本集):
#!/bin/bash
# MongoDB自动化备份脚本(支持单实例和副本集)
set -euo pipefail
# 配置
BACKUP_BASE="/backup/mongodb"
MONGO_HOST="localhost:27017"
MONGO_USER="backupuser"
MONGO_PASS="securepass"
MONGO_AUTH_DB="admin"
RETENTION_DAYS=30
S3_BUCKET="my-mongodb-backups"
NOTIFY_EMAIL="dba@example.com"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_BASE/backup.log"
}
error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$BACKUP_BASE/backup.log" >&2
echo "Backup failed: $1" | mail -s "MongoDB Backup Alert" "$NOTIFY_EMAIL"
exit 1
}
# 检查MongoDB连接
check_mongodb() {
if ! mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase "$MONGO_AUTH_DB" --eval "db.adminCommand('ping')" > /dev/null 2>&1; then
error "Cannot connect to MongoDB at $MONGO_HOST"
fi
log "MongoDB connection verified"
}
# 检查磁盘空间
check_disk_space() {
local required_gb=50 # 根据数据量调整
local available_gb=$(df "$BACKUP_BASE" | awk 'NR==2 {print $4/1024/1024}')
if (( $(echo "$available_gb < $required_gb" | bc -l) )); then
error "Insufficient disk space: ${available_gb}GB available, ${required_gb}GB required"
fi
log "Disk space check passed: ${available_gb}GB available"
}
# 执行备份
perform_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="$BACKUP_BASE/$timestamp"
local archive_file="$backup_dir/mongodb.archive"
mkdir -p "$backup_dir"
log "Starting backup to $archive_file"
# 使用oplog实现时间点恢复能力
if mongodump \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase "$MONGO_AUTH_DB" \
--oplog \
--gzip \
--archive="$archive_file" \
2>> "$BACKUP_BASE/backup.log"; then
log "Backup completed successfully: $archive_file"
echo "$archive_file"
else
error "mongodump failed with exit code $?"
fi
}
# 上传到S3
upload_to_s3() {
local archive_file="$1"
local s3_path="s3://$S3_BUCKET/$(basename $(dirname $archive_file))/$(basename $archive_file)"
log "Uploading to S3: $s3_path"
if aws s3 cp "$archive_file" "$s3_path" --storage-class STANDARD_IA 2>> "$BACKUP_BASE/backup.log"; then
log "S3 upload completed"
else
error "S3 upload failed"
fi
}
# 验证备份
verify_backup() {
local archive_file="$1"
log "Verifying backup integrity"
# 测试解压(不实际恢复)
if mongorestore \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase "$MONGO_AUTH_DB" \
--gzip \
--archive="$archive_file" \
--dryRun \
2>> "$BACKUP_BASE/backup.log"; then
log "Backup verification passed"
else
error "Backup verification failed"
fi
}
# 清理旧备份
cleanup_old_backups() {
log "Cleaning up backups older than $RETENTION_DAYS days"
# 本地清理
find "$BACKUP_BASE" -type d -mtime "+$RETENTION_DAYS" -exec rm -rf {} + 2>> "$BACKUP_BASE/backup.log"
# S3清理(保留最近30天)
aws s3 ls "s3://$S3_BUCKET/" | while read -r line; do
local date_str=$(echo "$line" | awk '{print $1}')
local dir=$(echo "$line" | awk '{print $2}' | sed 's/\///')
if [[ -n "$date_str" && -n "$dir" ]]; then
local days_old=$(( ( $(date +%s) - $(date -d "$date_str" +%s) ) / 86400 ))
if [[ $days_old -gt $RETENTION_DAYS ]]; then
log "Deleting old S3 backup: $dir"
aws s3 rm "s3://$S3_BUCKET/$dir" --recursive
fi
fi
done
log "Cleanup completed"
}
# 主函数
main() {
log "=== MongoDB Backup Started ==="
check_mongodb
check_disk_space
local archive_file
archive_file=$(perform_backup)
verify_backup "$archive_file"
upload_to_s3 "$archive_file"
cleanup_old_backups
log "=== MongoDB Backup Completed Successfully ==="
# 发送成功通知
echo "MongoDB backup completed successfully: $(basename $archive_file)" | mail -s "MongoDB Backup Success" "$NOTIFY_EMAIL"
}
# 错误处理
trap 'error "Script interrupted"' INT TERM
# 运行主函数
main "$@"
使用systemd定时任务:
# /etc/systemd/system/mongodb-backup.service
[Unit]
Description=MongoDB Daily Backup
After=network.target
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/mongodb-backup.sh
Environment="AWS_ACCESS_KEY_ID=your_key"
Environment="AWS_SECRET_ACCESS_KEY=your_secret"
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/mongodb-backup.timer
[Unit]
Description=MongoDB Backup Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
启用定时任务:
sudo systemctl enable mongodb-backup.timer
sudo systemctl start mongodb-backup.timer
4. 备份监控与告警
备份监控脚本:
#!/bin/bash
# MongoDB备份监控脚本
BACKUP_BASE="/backup/mongodb"
LOG_FILE="$BACKUP_BASE/backup.log"
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
PROMETHEUS_PUSHGATEWAY="http://prometheus-pushgateway:9091"
# 检查最近备份状态
check_last_backup() {
local last_backup=$(find "$BACKUP_BASE" -name "*.archive" -type f -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -1 | cut -d' ' -f2-)
if [[ -z "$last_backup" ]]; then
send_alert "CRITICAL: No backup files found in $BACKUP_BASE"
return 1
fi
local backup_age=$(( $(date +%s) - $(stat -c %Y "$last_backup") ))
local max_age=$(( 26 * 3600 )) # 26小时
if [[ $backup_age -gt $max_age ]]; then
send_alert "WARNING: Last backup is $(($backup_age / 3600)) hours old: $last_backup"
return 1
fi
# 检查备份大小是否合理(至少100MB)
local backup_size=$(stat -c %s "$last_backup")
if [[ $backup_size -lt 104857600 ]]; then
send_alert "WARNING: Backup file too small: $(($backup_size / 1024 / 1024))MB"
return 1
fi
echo "OK: Last backup $(basename $last_backup) is $(($backup_age / 3600)) hours old, size $(($backup_size / 1024 / 1024))MB"
return 0
}
# 发送Slack告警
send_slack() {
local message="$1"
local payload="{\"text\":\"$message\"}"
curl -X POST -H 'Content-type: application/json' --data "$payload" "$SLACK_WEBHOOK" > /dev/null 2>&1
}
# 发送Prometheus指标
send_metrics() {
local status=$1
local age=$2
local size=$3
cat <<EOF | curl --data-binary @- "$PROMETHEUS_PUSHGATEWAY/metrics/job/mongodb_backup" > /dev/null 2>&1
# HELP mongodb_backup_status Backup status (1=success, 0=failure)
# TYPE mongodb_backup_status gauge
mongodb_backup_status $status
# HELP mongodb_backup_age_seconds Age of last backup in seconds
# TYPE mongodb_backup_age_seconds gauge
mongodb_backup_age_seconds $age
# HELP mongodb_backup_size_bytes Size of last backup in bytes
# TYPE mongodb_backup_size_bytes gauge
mongodb_backup_size_bytes $size
EOF
}
# 主监控逻辑
main() {
local result
result=$(check_last_backup)
local status=$?
if [[ $status -eq 0 ]]; then
# 解析成功信息
local age=$(echo "$result" | grep -oP '\d+(?= hours)')
local size=$(echo "$result" | grep -oP '\d+(?=MB)')
send_metrics 1 "$((age * 3600))" "$((size * 1024 * 1024))"
echo "$result"
else
send_metrics 0 0 0
send_slack "$result"
echo "$result"
exit 1
fi
}
main "$@"
备份验证与测试恢复
1. 备份验证策略
定期验证脚本:
#!/bin/bash
# MongoDB备份验证脚本
BACKUP_BASE="/backup/mongodb"
TEST_DB="backup_verification_$(date +%Y%m%d)"
MONGO_HOST="localhost:27017"
MONGO_USER="testuser"
MONGO_PASS="testpass"
# 获取最新备份
LATEST_BACKUP=$(find "$BACKUP_BASE" -name "*.archive" -type f -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)
if [[ -z "$LATEST_BACKUP" ]]; then
echo "No backup found"
exit 1
fi
echo "Verifying backup: $LATEST_BACKUP"
# 1. 检查备份文件完整性
if ! mongorestore --gzip --archive="$LATEST_BACKUP" --dryRun 2>&1 | grep -q "done"; then
echo "Backup file is corrupted"
exit 1
fi
# 2. 恢复到测试数据库
echo "Restoring to test database: $TEST_DB"
if mongorestore \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase admin \
--gzip \
--archive="$LATEST_BACKUP" \
--nsFrom 'myapp.*' \
--nsTo "$TEST_DB.*" \
2>&1; then
echo "Restore completed successfully"
# 3. 验证数据完整性
echo "Verifying data integrity..."
# 检查文档数量
local_count=$(mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('myapp').users.countDocuments()" | tail -1)
test_count=$(mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('$TEST_DB').users.countDocuments()" | tail -1)
if [[ "$local_count" == "$test_count" ]]; then
echo "Document count matches: $local_count"
else
echo "Document count mismatch: original=$local_count, restored=$test_count"
exit 1
fi
# 4. 清理测试数据库
echo "Cleaning up test database..."
mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('$TEST_DB').dropDatabase()"
echo "Backup verification passed"
else
echo "Restore failed"
exit 1
fi
2. 恢复演练计划
季度恢复演练脚本:
#!/bin/bash
# MongoDB季度恢复演练脚本
# 配置
BACKUP_BASE="/backup/mongodb"
RESTORE_DIR="/tmp/restore_test_$(date +%Y%m%d)"
MONGO_HOST="localhost:27017"
MONGO_USER="restoreuser"
MONGO_PASS="restorepass"
DURATION_FILE="$BACKUP_BASE/restore_times.txt"
# 创建恢复目录
mkdir -p "$RESTORE_DIR"
# 选择一个随机备份(模拟真实恢复场景)
BACKUP_LIST=($(find "$BACKUP_BASE" -name "*.archive" -type f | shuf | head -1))
BACKUP_FILE="${BACKUP_LIST[0]}"
if [[ -z "$BACKUP_FILE" ]]; then
echo "No backup file found"
exit 1
fi
echo "Starting restore drill with: $BACKUP_FILE"
echo "Start time: $(date)"
# 记录开始时间
start_time=$(date +%s)
# 执行恢复(使用不同数据库名避免冲突)
RESTORE_DB="drill_$(date +%Y%m%d_%H%M%S)"
if mongorestore \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase admin \
--gzip \
--archive="$BACKUP_FILE" \
--nsFrom 'myapp.*' \
--nsTo "$RESTORE_DB.*" \
--numInsertionWorkersPerCollection=8 \
--drop; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "Restore completed in $duration seconds"
echo "$duration" >> "$DURATION_FILE"
# 验证恢复数据
original_count=$(mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('myapp').users.countDocuments()" | tail -1)
restored_count=$(mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('$RESTORE_DB').users.countDocuments()" | tail -1)
if [[ "$original_count" == "$restored_count" ]]; then
echo "SUCCESS: Data integrity verified. Documents: $restored_count"
# 计算平均恢复时间
avg_time=$(awk '{sum+=$1; count++} END {print sum/count}' "$DURATION_FILE")
echo "Average restore time: $avg_time seconds"
# 清理
mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "db.getSiblingDB('$RESTORE_DB').dropDatabase()"
# 发送报告
echo "Restore drill completed successfully. Duration: ${duration}s, Average: ${avg_time}s" | mail -s "MongoDB Restore Drill Report" dba@example.com
else
echo "FAILED: Data mismatch. Original: $original_count, Restored: $restored_count"
exit 1
fi
else
echo "Restore failed"
exit 1
fi
高级备份策略
1. 增量备份与时间点恢复(PITR)
使用oplog实现增量备份:
#!/bin/bash
# MongoDB增量备份脚本(基于oplog)
# 配置
OPLOG_BASE="/backup/mongodb/oplog"
MONGO_HOST="localhost:27017"
MONGO_USER="backupuser"
MONGO_PASS="securepass"
LAST_BACKUP_FILE="$OPLOG_BASE/last_backup.txt"
# 获取上次备份的oplog时间戳
get_last_ts() {
if [[ -f "$LAST_BACKUP_FILE" ]]; then
cat "$LAST_BACKUP_FILE"
else
# 获取当前oplog最早条目时间戳
mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "
const oplog = db.getSiblingDB('local').oplog.rs;
const first = oplog.find().sort({\$natural: 1}).limit(1).next();
printjson(first.ts);
" | tr -d '\n'
fi
}
# 执行增量备份
perform_incremental_backup() {
local last_ts=$(get_last_ts)
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="$OPLOG_BASE/incremental/$timestamp"
mkdir -p "$backup_dir"
echo "Backing up oplog since: $last_ts"
# 导出oplog片段
mongodump \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase admin \
--db local \
--collection oplog.rs \
--query "{ ts: { \$gte: $last_ts } }" \
--out "$backup_dir" \
--gzip
# 保存当前时间戳
mongosh --quiet --host "$MONGO_HOST" --username "$MONGO_USER" --password "$MONGO_PASS" --authenticationDatabase admin --eval "
const oplog = db.getSiblingDB('local').oplog.rs;
const last = oplog.find().sort({\$natural: -1}).limit(1).next();
printjson(last.ts);
" > "$LAST_BACKUP_FILE"
echo "Incremental backup completed: $backup_dir"
}
# 恢复到指定时间点
restore_to_point_in_time() {
local target_ts="$1" # 格式: Timestamp(1234567890, 1)
local base_backup="$2" # 全量备份路径
echo "Restoring to point in time: $target_ts"
# 1. 恢复全量备份
mongorestore \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase admin \
--gzip \
--archive="$base_backup"
# 2. 应用oplog
for oplog_file in $(find "$OPLOG_BASE/incremental" -name "*.bson.gz" | sort); do
echo "Applying oplog: $oplog_file"
mongorestore \
--host "$MONGO_HOST" \
--username "$MONGO_USER" \
--password "$MONGO_PASS" \
--authenticationDatabase admin \
--oplogReplay \
--gzip \
--archive="$oplog_file"
done
}
# 主函数
case "${1:-}" in
backup)
perform_incremental_backup
;;
restore)
if [[ -z "$2" || -z "$3" ]]; then
echo "Usage: $0 restore <target_ts> <base_backup>"
exit 1
fi
restore_to_point_in_time "$2" "$3"
;;
*)
echo "Usage: $0 {backup|restore <target_ts> <base_backup>}"
exit 1
;;
esac
2. 备份加密
使用GPG加密备份:
#!/bin/bash
# MongoDB备份加密脚本
BACKUP_DIR="/backup/mongodb"
ENCRYPTED_DIR="/backup/mongodb/encrypted"
GPG_RECIPIENT="dba@example.com"
# 加密备份
encrypt_backup() {
local backup_file="$1"
local encrypted_file="$ENCRYPTED_DIR/$(basename $backup_file).gpg"
gpg --encrypt --recipient "$GPG_RECIPIENT" --output "$encrypted_file" "$backup_file"
echo "Encrypted: $encrypted_file"
# 删除原始文件(可选)
# rm -f "$backup_file"
}
# 解密备份
decrypt_backup() {
local encrypted_file="$1"
local output_file="$2"
gpg --decrypt --output "$output_file" "$encrypted_file"
echo "Decrypted: $output_file"
}
# 主函数
case "${1:-}" in
encrypt)
encrypt_backup "$2"
;;
decrypt)
decrypt_backup "$2" "$3"
;;
*)
echo "Usage: $0 {encrypt <file>|decrypt <encrypted_file> <output_file>}"
exit 1
;;
esac
使用MongoDB加密备份(企业版特性):
# MongoDB企业版支持加密备份
mongodump \
--host localhost \
--port 27017 \
--username backupuser \
--password securepass \
--authenticationDatabase admin \
--out /backup/mongodb/encrypted \
--enableEncryption \
--encryptionKeyFile /path/to/keyfile
3. 云原生备份方案
使用Kubernetes备份(Velero):
# velero-backup.yaml
apiVersion: velero.io/v1
kind: Backup
metadata:
name: mongodb-backup-$(date +%Y%m%d)
namespace: velero
spec:
includedNamespaces:
- mongodb
includedResources:
- pods
- persistentvolumeclaims
- persistentvolumes
- configmaps
- secrets
snapshotVolumes: true
ttl: 720h0m0s # 30天
hooks:
resources:
- name: mongodb-pre-backup
includedNamespaces:
- mongodb
includedResources:
- pods
labelSelector:
matchLabels:
app: mongodb
pre:
- exec:
container: mongodb
command:
- /bin/bash
- -c
- |
mongod --dbpath /data/db --repair --repairpath /data/db/repair
sleep 30
使用AWS Backup for MongoDB:
{
"BackupPlan": {
"BackupPlanName": "MongoDB-Daily-Backup",
"Rules": [
{
"RuleName": "DailyBackups",
"TargetBackupVaultName": "MongoDB-Vault",
"ScheduleExpression": "cron(0 2 * * ? *)",
"StartWindowMinutes": 60,
"CompletionWindowMinutes": 180,
"Lifecycle": {
"DeleteAfterDays": 30
},
"RecoveryPointTags": {
"Database": "MongoDB",
"Environment": "Production"
}
}
]
}
}
备份最佳实践总结
1. 备份策略检查清单
- [ ] 定期测试恢复:至少每季度执行一次完整的恢复演练
- [ ] 多副本存储:本地 + 云存储 + 异地
- [ ] 加密所有备份:使用GPG或云服务加密
- [ ] 监控与告警:实时监控备份状态和磁盘空间
- [ ] 文档化流程:编写详细的恢复操作手册
- [ ] 权限最小化:使用专用备份账户,限制权限
- [ ] 保留oplog:至少保留24小时oplog用于PITR
- [ ] 分片集群特殊处理:确保所有分片和配置服务器一致备份
- [ ] 备份窗口优化:在业务低峰期执行备份
- [ ] 版本兼容性:确保备份工具与MongoDB版本匹配
2. 常见陷阱与避免方法
陷阱1:忽略索引备份
- 问题:
mongodump导出索引定义,但恢复时需要重建索引,耗时很长 - 解决:使用文件系统快照或在业务低峰期恢复
陷阱2:分片集群备份不一致
- 问题:各分片备份时间点不同,导致数据不一致
- 解决:使用
--oplog选项并协调所有分片同时备份
陷阱3:备份文件未验证
- 问题:备份文件损坏但未被发现,恢复时失败
- 解决:定期执行
mongorestore --dryRun验证
陷阱4:磁盘空间不足
- 问题:备份过程中磁盘写满导致失败
- 解决:监控磁盘空间,预留足够缓冲
陷阱5:备份窗口过长
- 问题:备份影响业务性能
- 解决:使用增量备份、分片并行备份或文件系统快照
3. 性能优化建议
mongodump优化:
# 并行导出多个集合
mongodump --host localhost --port 27017 --db myapp --out /backup/mongodb/ &
mongodump --host localhost --port 27017 --db otherdb --out /backup/mongodb/ &
wait
# 使用管道压缩(减少磁盘IO)
mongodump --host localhost --port 27017 --db myapp --archive=/backup/mongodb/myapp.archive --gzip &
mongorestore优化:
# 并行恢复
mongorestore --host localhost --port 27017 --numInsertionWorkersPerCollection=8 --db myapp /backup/mongodb/myapp/
# 禁用索引创建(恢复后手动创建)
mongorestore --host localhost --port 27017 --noIndexRestore --db myapp /backup/mongodb/myapp/
# 然后手动创建索引
mongosh --eval "db.users.createIndex({created_at: -1})"
文件系统快照优化:
# 使用XFS或ZFS(支持快照)
# XFS示例
xfs_snapshot -c /data/db /backup/db_snapshot
# ZFS示例
zfs snapshot rpool/data/db@backup_$(date +%Y%m%d)
zfs send rpool/data/db@backup_$(date +%Y%m%d) > /backup/mongodb/zfs_snapshot
结论
MongoDB备份策略的制定需要综合考虑业务需求、数据规模、基础设施和恢复目标。没有一种万能的方案,最佳实践是采用混合策略:
- 日常运营:使用
mongodump+ oplog实现灵活备份和PITR - 灾难恢复:使用文件系统快照实现快速恢复
- 云环境:利用MongoDB Atlas或云服务商的托管备份服务
- 合规性:定期归档备份并加密存储
最重要的是,备份的价值在于恢复。无论您的备份策略多么完善,如果无法在需要时成功恢复数据,一切都是徒劳。因此,请务必:
- 定期测试恢复(至少每季度一次)
- 文档化所有恢复流程
- 建立明确的RPO和RTO目标
- 持续监控和优化备份性能
通过本文提供的详细脚本和最佳实践,您应该能够制定出适合自己业务场景的高效MongoDB备份计划,有效应对数据丢失风险与恢复挑战。记住,数据安全是持续的过程,而非一次性的任务。
