Linux 备份策略
大约 11 分钟约 3380 字
Linux 备份策略
简介
Linux 备份策略涵盖全量/增量备份、定时任务、远程备份和灾难恢复。完善的备份策略是数据安全的最后防线,确保在硬件故障、误操作或勒索软件攻击后能恢复数据。
备份的核心目标可以用两个指标衡量:RPO(Recovery Point Objective,恢复点目标)和 RTO(Recovery Time Objective,恢复时间目标)。RPO 决定了你最多能接受丢失多少数据(例如:RPO = 1 小时表示最多丢失 1 小时的数据),RTO 决定了从故障发生到恢复服务最长需要多少时间。
3-2-1 备份原则
3-2-1 原则(业界标准):
┌──────────────────────────────────┐
│ 3 份数据副本 │
│ 2 种存储介质(磁盘 + 磁带/云) │
│ 1 份异地(离线/异地机房/云存储) │
└──────────────────────────────────┘
备份类型对比:
┌──────────────┬──────────────┬──────────────┐
│ 全量备份 │ 增量备份 │ 差异备份 │
├──────────────┼──────────────┼──────────────┤
│ 备份所有数据 │ 只备份变化 │ 备份上次全量 │
│ 恢复简单 │ 恢复复杂 │ 后的所有变化 │
│ 耗时耗空间 │ 耗时少空间小 │ 恢复较快 │
│ 每周一次 │ 每天一次 │ 每天一次 │
└──────────────┴──────────────┴──────────────┘特点
实现
tar 全量与增量备份
# 全量备份
tar -czpf /backup/full_$(date +%Y%m%d).tar.gz \
--exclude='/proc' \
--exclude='/sys' \
--exclude='/dev' \
--exclude='/tmp' \
--exclude='/backup' \
--exclude='/lost+found' \
--exclude='/run' \
--exclude='/mnt' \
--exclude='/media' \
/etc /home /opt/myapp /var/lib/mysql
# 参数说明:
# -c 创建新归档
# -z 使用 gzip 压缩
# -p 保留权限信息
# -f 指定输出文件名
# 查看备份内容
tar -tzf /backup/full_20240115.tar.gz | head -20
# 恢复(注意:恢复到根目录会覆盖现有文件)
tar -xzpf /backup/full_20240115.tar.gz -C /restore/
# 恢复前先检查是否会造成冲突
tar -tzf /backup/full_20240115.tar.gz | grep "^etc/ssh/sshd_config"
# 恢复单个文件
tar -xzpf /backup/full_20240115.tar.gz -C /tmp/ etc/ssh/sshd_config
# 查看备份文件大小
ls -lh /backup/full_*.tar.gz
du -sh /backup/# 增量备份(基于 GNU tar)
# 第一次:全量备份(level 0)
tar -czpf /backup/full_$(date +%Y%m%d).tar.gz \
--listed-incremental=/backup/snapshot.snar \
--exclude='/proc' --exclude='/sys' --exclude='/dev' \
/etc /home /opt/myapp
# 第二次:增量备份(level 1,只备份变化的部分)
tar -czpf /backup/incr_$(date +%Y%m%d).tar.gz \
--listed-incremental=/backup/snapshot.snar \
--exclude='/proc' --exclude='/sys' --exclude='/dev' \
/etc /home /opt/myapp
# 注意:--listed-incremental 使用的快照文件(.snar)必须保留
# 恢复时需要按顺序恢复:先全量,再增量1,再增量2...
# 恢复流程
tar -xzpf /backup/full_20240115.tar.gz -C /restore/
tar -xzpf /backup/incr_20240116.tar.gz -C /restore/
tar -xzpf /backup/incr_20240117.tar.gz -C /restore/
# 使用 --level 参数控制增量级别
# level 0 = 全量, level 1 = 相对于上次全量的增量
tar -czpf /backup/backup.tar.gz \
--listed-incremental=/backup/snapshot.snar \
--level=0 \
/datarsync 远程备份
# rsync 基本语法
# rsync [选项] 源路径 目标路径
# 本地到远程同步
rsync -avz --delete /opt/myapp/ user@backup-server:/backup/myapp/
# 增量同步(只传输变化部分)
rsync -avz --backup --backup-dir=/backup/incremental_$(date +%Y%m%d) \
/data/ user@backup-server:/backup/current/
# 排除不需要的文件
rsync -avz --exclude='*.log' --exclude='node_modules' --exclude='.git' \
/project/ user@backup-server:/backup/project/
# 带带宽限制的备份(单位 KB/s)
rsync -avz --bwlimit=5000 /data/ user@backup-server:/backup/data/
# 使用 SSH 密钥免密
rsync -avz -e "ssh -i ~/.ssh/backup_key -p 22" /data/ user@backup-server:/backup/
# 显示传输进度
rsync -avz --progress /data/ user@backup-server:/backup/data/
# 使用 --partial 保留部分传输的文件(断点续传)
rsync -avz --partial --progress /large-file user@backup-server:/backup/# rsync 常用选项详解
# -a 归档模式(保留权限、时间戳等)
# -v 显示详细输出
# -z 传输时压缩
# --delete 删除目标端源端不存在的文件
# --backup 备份被覆盖的文件
# --exclude 排除文件/目录
# --include 包含文件/目录(配合 exclude 使用)
# -n 模拟运行(不实际执行,只显示会做什么)
# --stats 显示传输统计信息
# 模拟运行(先看看会发生什么)
rsync -avzn --delete /data/ user@backup-server:/backup/data/
# 排除和包含的组合使用
rsync -avz \
--exclude='*.tmp' \
--include='*.sql' \
--include='*/' \
--exclude='*' \
/data/ user@backup-server:/backup/data/自动化备份脚本
#!/bin/bash
# /opt/scripts/backup.sh
set -euo pipefail
# 配置
BACKUP_DIR="/backup"
RETAIN_DAYS=30
REMOTE="user@backup-server:/backup"
LOG="/var/log/backup.log"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_PWD="${MYSQL_ROOT_PASSWORD:-}"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >> "$LOG"; }
# 数据库备份
backup_mysql() {
log "Starting MySQL backup..."
if command -v mysqldump &>/dev/null; then
mysqldump -u root -p"${MYSQL_PWD}" --all-databases \
--single-transaction --routines --triggers --events \
2>>"$LOG" | gzip > "${BACKUP_DIR}/mysql_${DATE}.sql.gz"
log "MySQL backup completed: $(ls -lh ${BACKUP_DIR}/mysql_${DATE}.sql.gz | awk '{print $5}')"
else
log "MySQL not installed, skipping MySQL backup"
fi
}
# PostgreSQL 备份
backup_postgresql() {
log "Starting PostgreSQL backup..."
if command -v pg_dumpall &>/dev/null; then
pg_dumpall | gzip > "${BACKUP_DIR}/postgresql_${DATE}.sql.gz"
log "PostgreSQL backup completed: $(ls -lh ${BACKUP_DIR}/postgresql_${DATE}.sql.gz | awk '{print $5}')"
else
log "PostgreSQL not installed, skipping PostgreSQL backup"
fi
}
# 文件备份
backup_files() {
log "Starting file backup..."
tar -czpf "${BACKUP_DIR}/files_${DATE}.tar.gz" \
/etc /home /opt/myapp /var/log/app \
2>>"$LOG"
log "File backup completed: $(ls -lh ${BACKUP_DIR}/files_${DATE}.tar.gz | awk '{print $5}')"
}
# MongoDB 备份
backup_mongodb() {
log "Starting MongoDB backup..."
if command -v mongodump &>/dev/null; then
mongodump --gzip --out "${BACKUP_DIR}/mongodb_${DATE}" 2>>"$LOG"
tar -czpf "${BACKUP_DIR}/mongodb_${DATE}.tar.gz" -C "${BACKUP_DIR}" "mongodb_${DATE}"
rm -rf "${BACKUP_DIR}/mongodb_${DATE}"
log "MongoDB backup completed: $(ls -lh ${BACKUP_DIR}/mongodb_${DATE}.tar.gz | awk '{print $5}')"
else
log "MongoDB not installed, skipping MongoDB backup"
fi
}
# 远程同步
sync_remote() {
log "Syncing to remote server..."
rsync -avz --delete --bwlimit=10000 \
"${BACKUP_DIR}/" "${REMOTE}/" \
2>>"$LOG"
log "Remote sync completed"
}
# 清理旧备份
cleanup() {
log "Cleaning old backups (older than ${RETAIN_DAYS} days)..."
local deleted=0
deleted=$(find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +${RETAIN_DAYS} -print -delete 2>/dev/null | wc -l)
deleted=$((deleted + $(find "${BACKUP_DIR}" -name "*.sql.gz" -mtime +${RETAIN_DAYS} -print -delete 2>/dev/null | wc -l)))
log "Deleted ${deleted} old backup files"
}
# 发送通知
notify() {
local subject=$1
local body=$2
# 简单的邮件通知(需配置 mailx 或 sendmail)
if command -v mail &>/dev/null; then
echo "$body" | mail -s "$subject" admin@example.com
fi
# 也可以使用钉钉/企业微信 Webhook
}
# 主流程
main() {
log "========== Backup started =========="
local start_time=$(date +%s)
backup_mysql && backup_postgresql && backup_mongodb && backup_files
sync_remote
cleanup
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log "Backup completed in ${duration} seconds"
# 检查备份文件大小
local total_size=$(du -sh ${BACKUP_DIR} | awk '{print $1}')
log "Total backup size: ${total_size}"
notify "备份完成" "备份耗时 ${duration} 秒,总大小 ${total_size}"
}
main "$@"Crontab 定时任务
# 编辑定时任务
crontab -e
# 备份计划示例:
# 每天凌晨 2 点执行完整备份
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup_cron.log 2>&1
# 每周日凌晨 3 点执行全量文件备份
0 3 * * 0 tar -czpf /backup/weekly_full_$(date +\%Y\%m\%d).tar.gz /etc /home /opt
# 每小时增量备份 MySQL
0 * * * * mysqldump -u root -pPassword --single-transaction mydb | gzip > /backup/mysql_hourly_$(date +\%Y\%m\%d_\%H).sql.gz
# 每天清理 30 天前的旧备份
0 4 * * * find /backup -name "*.sql.gz" -mtime +30 -delete
# 查看 crontab
crontab -l
# 查看 cron 执行日志
grep CRON /var/log/syslog
tail -100 /var/log/backup_cron.log
# crontab 时间格式:
# ┌───────── 分钟 (0-59)
# │ ┌───────── 小时 (0-23)
# │ │ ┌───────── 日期 (1-31)
# │ │ │ ┌───────── 月份 (1-12)
# │ │ │ │ ┌───────── 星期 (0-7, 0和7都是周日)
# │ │ │ │ │
# * * * * * command优点
缺点
总结
备份策略应遵循 3-2-1 原则:3 份数据、2 种介质、1 份异地。rsync 实现增量同步减少带宽消耗。定期验证备份可恢复性。建议自动化备份并通过监控确认每次成功。
数据库备份
MySQL 备份与恢复
# 全量备份(使用 mysqldump)
mysqldump -u root -p --single-transaction --routines --triggers \
--all-databases > /backup/mysql_full_$(date +%Y%m%d).sql
# 单库备份
mysqldump -u root -p --single-transaction mydb > /backup/mydb_$(date +%Y%m%d).sql
# 只备份表结构
mysqldump -u root -p --no-data mydb > /backup/mydb_schema.sql
# 只备份指定表
mysqldump -u root -p mydb users orders > /backup/mydb_tables.sql
# 压缩备份(节省空间)
mysqldump -u root -p --single-transaction mydb | gzip > /backup/mydb_$(date +%Y%m%d).sql.gz
# 恢复数据
mysql -u root -p mydb < /backup/mydb_20260414.sql
# 从压缩文件恢复
gunzip < /backup/mydb_20260414.sql.gz | mysql -u root -p mydb
# 自动化 MySQL 备份脚本
cat > /usr/local/bin/mysql_backup.sh << 'SCRIPT'
#!/bin/bash
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_USER="backup_user"
MYSQL_PASS="BackupPass123!"
KEEP_DAYS=30
mkdir -p $BACKUP_DIR
# 全量备份
mysqldump -u $MYSQL_USER -p$MYSQL_PASS \
--single-transaction --routines --triggers \
--all-databases | gzip > $BACKUP_DIR/mysql_full_${DATE}.sql.gz
# 验证备份文件
if [ $? -eq 0 ] && [ -s "$BACKUP_DIR/mysql_full_${DATE}.sql.gz" ]; then
echo "[$(date)] MySQL 全量备份成功: mysql_full_${DATE}.sql.gz"
else
echo "[$(date)] MySQL 全量备份失败!" | mail -s "MySQL 备份失败" admin@example.com
fi
# 清理旧备份
find $BACKUP_DIR -name "mysql_full_*.sql.gz" -mtime +$KEEP_DAYS -delete
SCRIPT
chmod +x /usr/local/bin/mysql_backup.sh
# 每天凌晨 2 点执行
echo "0 2 * * * /usr/local/bin/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1" | crontab -PostgreSQL 备份
# 全量备份
pg_dump -U postgres --clean --if-exists --create mydb > /backup/mydb_$(date +%Y%m%d).sql
# 自定义格式备份(支持并行恢复)
pg_dump -U postgres -Fc mydb > /backup/mydb_$(date +%Y%m%d).dump
# 并行备份(大数据库)
pg_dump -U postgres -j 4 -Fd mydb -f /backup/mydb_dir_$(date +%Y%m%d)
# 恢复
psql -U postgres < /backup/mydb_20260414.sql
pg_restore -U postgres -d mydb /backup/mydb_20260414.dumpDocker 容器备份
# 备份容器数据卷
docker run --rm \
-v myapp_data:/data \
-v /backup:/backup \
alpine tar czf /backup/myapp_data_$(date +%Y%m%d).tar.gz -C /data .
# 备份容器镜像
docker save myapp:1.0 | gzip > /backup/myapp_1.0_$(date +%Y%m%d).tar.gz
# 恢复镜像
docker load < /backup/myapp_1.0_20260414.tar.gz
# 恢复数据卷
docker run --rm \
-v myapp_data:/data \
-v /backup:/backup \
alpine tar xzf /backup/myapp_data_20260414.tar.gz -C /data
# 导出容器为镜像(包含数据)
docker commit myapp-container myapp:backup_$(date +%Y%m%d)
docker save myapp:backup_20260414 | gzip > /backup/myapp_backup_20260414.tar.gz远程备份方案
rsync 远程同步
# 从本地同步到远程服务器
rsync -avz --delete \
/data/app/ \
backup@192.168.1.200:/backup/app/
# 通过 SSH 指定端口
rsync -avz -e "ssh -p 2222" \
/data/app/ \
backup@192.168.1.200:/backup/app/
# 排除文件
rsync -avz --delete \
--exclude='*.log' \
--exclude='tmp/' \
--exclude='.git/' \
/data/app/ \
backup@192.168.1.200:/backup/app/
# 带进度和限速
rsync -avz --progress --bwlimit=5000 \
/data/app/ \
backup@192.168.1.200:/backup/app/
# 远程到远程同步(通过本地中转)
rsync -avz backup1@host1:/data/ backup2@host2:/backup/增量备份脚本
#!/bin/bash
# incremental_backup.sh — 增量备份脚本
# 基于 tar 的 GNU 增量备份
BACKUP_DIR="/backup"
SNAPSHOT_FILE="$BACKUP_DIR/.snapshot"
SOURCE_DIR="/data"
DATE=$(date +%Y%m%d)
DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday
# 周日全量备份,其余增量备份
if [ "$DAY_OF_WEEK" -eq 7 ]; then
echo "执行全量备份..."
tar -czpf $BACKUP_DIR/full_${DATE}.tar.gz \
--listed-incremental=$SNAPSHOT_FILE \
--exclude='*.log' \
--exclude='tmp/' \
$SOURCE_DIR
# 全量备份后重置快照文件
rm -f $SNAPSHOT_FILE
else
echo "执行增量备份..."
tar -czpf $BACKUP_DIR/inc_${DATE}.tar.gz \
--listed-incremental=$SNAPSHOT_FILE \
--exclude='*.log' \
--exclude='tmp/' \
$SOURCE_DIR
fi
# 验证备份
if [ $? -eq 0 ]; then
echo "备份成功: $(ls -lh $BACKUP_DIR/*_${DATE}.tar.gz)"
else
echo "备份失败!"
fi
# 清理超过 30 天的备份
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete备份监控与告警
#!/bin/bash
# backup_monitor.sh — 备份监控脚本
BACKUP_DIR="/backup"
ALERT_EMAIL="admin@example.com"
MIN_SIZE=1024 # 最小备份文件大小(KB)
echo "=== 备份监控报告 $(date) ==="
# 检查今天的备份文件是否存在
TODAY=$(date +%Y%m%d)
BACKUP_COUNT=$(find $BACKUP_DIR -name "*${TODAY}*" -type f | wc -l)
if [ "$BACKUP_COUNT" -eq 0 ]; then
echo "告警:今天没有备份文件!" | mail -s "备份告警:无备份" $ALERT_EMAIL
exit 1
fi
echo "今天的备份数量: $BACKUP_COUNT"
# 检查备份文件大小
find $BACKUP_DIR -name "*${TODAY}*" -type f | while read file; do
SIZE=$(du -k "$file" | awk '{print $1}')
if [ "$SIZE" -lt "$MIN_SIZE" ]; then
echo "告警:备份文件过小: $file (${SIZE}KB)" | \
mail -s "备份告警:文件过小" $ALERT_EMAIL
else
echo "正常: $file (${SIZE}KB)"
fi
done
# 检查磁盘使用率
DISK_USAGE=$(df -h $BACKUP_DIR | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
echo "告警:备份磁盘使用率 ${DISK_USAGE}%" | \
mail -s "磁盘告警" $ALERT_EMAIL
fi
# 检查备份目录总大小
TOTAL_SIZE=$(du -sh $BACKUP_DIR | awk '{print $1}')
echo "备份目录总大小: $TOTAL_SIZE"灾难恢复演练
#!/bin/bash
# disaster_recovery_test.sh — 灾难恢复演练脚本
RESTORE_DIR="/tmp/restore_test"
DATE=$(date +%Y%m%d)
echo "=== 灾难恢复演练 ==="
# 1. 创建临时恢复目录
mkdir -p $RESTORE_DIR
# 2. 解压最新全量备份
echo "解压最新备份..."
LATEST_BACKUP=$(ls -t /backup/full_*.tar.gz | head -1)
tar -xzf "$LATEST_BACKUP" -C $RESTORE_DIR
# 3. 应用增量备份(按日期顺序)
for inc in $(ls -t /backup/inc_*.tar.gz | tac); do
echo "应用增量备份: $inc"
tar -xzf "$inc" -C $RESTORE_DIR
done
# 4. 验证文件完整性
echo "验证文件完整性..."
find $RESTORE_DIR -type f | wc -l
du -sh $RESTORE_DIR
# 5. 验证关键文件存在
CRITICAL_FILES=("/data/app/config.json" "/data/app/data.db")
for file in "${CRITICAL_FILES[@]}"; do
if [ -f "$RESTORE_DIR/$file" ]; then
echo "OK: $file"
else
echo "FAIL: $file 不存在!"
fi
done
# 6. 清理
rm -rf $RESTORE_DIR
echo "=== 演练完成 ==="关键知识点
- 3-2-1 备份原则:3 份副本、2 种存储介质、1 份异地
- tar 的 --listed-incremental 实现 GNU 增量备份
- rsync --delete 确保目标与源一致(删除源端不存在的文件)
- mysqldump --single-transaction 保证 InnoDB 一致性快照
- RPO 和 RTO 是衡量备份策略有效性的核心指标
项目落地视角
- 数据库每天全量备份 + 文件定期同步
- 备份脚本添加错误通知(邮件/钉钉/企业微信)
- 每月至少验证一次恢复流程
- 监控备份任务执行状态和备份文件大小
- 备份文件加密存储,防止数据泄露
常见误区
- 备份了但从未验证恢复
- 备份文件和数据在同一磁盘
- 忘记清理旧备份导致磁盘满
- 不加密备份文件(尤其远程备份)
- 备份脚本没有错误处理和通知机制
进阶路线
- 学习 Borg/Restic 增量去重备份工具
- 研究快照备份(LVM/ZFS Snapshot)
- 了解对象存储(S3/MinIO)的备份策略
- 学习 Bare Metal Recovery(裸机恢复)
- 研究备份加密和密钥管理
适用场景
- 数据库定期备份
- 应用配置和数据备份
- 灾难恢复准备
- 合规性审计要求
落地建议
- 遵循 3-2-1 原则设计备份策略
- 自动化备份脚本 + 定时任务
- 定期验证恢复流程
- 备份文件加密并异地存储
排错清单
- 检查备份文件大小是否合理
- 确认远程同步是否成功
- 验证备份文件可以正确解压
- 检查 crontab 是否正常执行
- 验证恢复流程是否可行
复盘问题
- 从数据丢失到恢复需要多长时间?
- 备份的 RPO(恢复点目标)是多少?
- 最近一次恢复演练是什么时候?
- 如果备份服务器也被加密了怎么办?
