引言:为什么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的工作原理:

  1. 建立到MongoDB的连接并执行listDatabases命令(如果备份所有数据库)
  2. 对每个要备份的数据库执行listCollections命令
  3. 对每个集合,使用查询游标读取所有文档
  4. 将文档序列化为BSON格式并写入输出文件
  5. 同时导出集合索引定义(存储在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的工作原理:

  1. 读取BSON文件并解析为MongoDB文档
  2. 将文档插入到目标集合中
  3. 如果目标集合已存在,默认会合并数据(相同_id的文档会被覆盖)
  4. 恢复索引定义(除非指定–noIndexRestore)
  5. 恢复后需要重建索引(如果索引不存在)

优点:

  • 灵活性高,支持选择性恢复
  • 支持压缩和流式处理
  • 可以恢复到不同数据库/集合
  • 支持合并模式

缺点:

  • 恢复速度较慢(特别是索引重建)
  • 需要足够的磁盘空间(临时文件)
  • 大量数据恢复时可能影响性能

文件系统快照备份

文件系统快照备份是基于存储级别的物理备份,适用于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

文件系统快照的工作原理:

  1. 存储系统在特定时间点创建数据卷的快照
  2. 快照是只读的,包含该时间点的所有数据块
  3. 可以挂载快照并复制数据,或直接使用快照恢复

优点:

  • 速度极快(通常几分钟内完成)
  • 对数据库性能影响极小
  • 备份文件是完整的数据文件副本
  • 恢复速度极快(直接使用数据文件)

缺点:

  • 需要特定的存储基础设施(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备份策略的制定需要综合考虑业务需求、数据规模、基础设施和恢复目标。没有一种万能的方案,最佳实践是采用混合策略

  1. 日常运营:使用mongodump + oplog实现灵活备份和PITR
  2. 灾难恢复:使用文件系统快照实现快速恢复
  3. 云环境:利用MongoDB Atlas或云服务商的托管备份服务
  4. 合规性:定期归档备份并加密存储

最重要的是,备份的价值在于恢复。无论您的备份策略多么完善,如果无法在需要时成功恢复数据,一切都是徒劳。因此,请务必:

  • 定期测试恢复(至少每季度一次)
  • 文档化所有恢复流程
  • 建立明确的RPO和RTO目标
  • 持续监控和优化备份性能

通过本文提供的详细脚本和最佳实践,您应该能够制定出适合自己业务场景的高效MongoDB备份计划,有效应对数据丢失风险与恢复挑战。记住,数据安全是持续的过程,而非一次性的任务。