82、数据库备份自动化:定时备份、增量备份、备份验证、异地备份
说实话,做了这么多年嵌入式数据库开发,我见过太多因为备份不到位而翻车的案例了。有一次,一个工业控制系统的数据库突然崩溃,结果发现备份文件已经三个月没更新了——因为运维人员忘了手动执行。从那以后,我给自己定了个规矩:凡是生产环境,备份必须自动化。
这一章,我们就来聊聊怎么把备份这件事做得滴水不漏。定时备份、增量备份、备份验证、异地备份,这四个环节缺一不可。
为什么备份自动化这么重要?
你想想看,嵌入式设备往往部署在无人值守的环境里。工控机、车载系统、医疗设备……这些地方谁会天天盯着数据库?一旦出问题,手动恢复的成本高得吓人。
我个人习惯把备份策略分成四个层次:
- 定时备份:按固定周期做全量备份,比如每天凌晨2点
- 增量备份:在全量备份之间,只记录变化的数据
- 备份验证:备份完了得检查文件是否完整、能否恢复
- 异地备份:把备份文件传到另一个物理位置,防止单点故障
这四个层次叠加起来,才能算得上一个靠谱的备份方案。
核心架构:备份自动化流程
下面这张图是我自己总结的备份自动化流程,你可以把它当作一个模板来用:
这个流程的核心思想是:定时触发 → 判断备份类型 → 执行备份 → 验证 → 异地传输。每一步都不能跳过,尤其是验证这一步,很多人会忽略。
定时备份:用cron搞定
在Linux嵌入式系统里,cron是最常用的定时任务工具。我一般这样配置:
# 每天凌晨2点执行全量备份
0 2 * * * /usr/local/bin/db_backup.sh full
# 每6小时执行一次增量备份
0 */6 * * * /usr/local/bin/db_backup.sh incremental
这里要注意一点:cron的环境变量和交互式shell不一样。我曾经踩过这个坑——脚本在终端跑得好好的,放到cron里就报错。后来发现是PATH没设置对。所以建议在脚本开头显式指定路径:
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export DB_HOME=/opt/database
增量备份:只存变化的数据
全量备份虽然完整,但太占空间。增量备份只记录从上一次备份以来的变化,效率高得多。
在SQLite这种嵌入式数据库里,我常用的做法是:
- 使用
.backup命令做全量备份 - 使用WAL(Write-Ahead Log)文件做增量备份
- 记录每个表的最后修改时间戳
下面是一个简单的增量备份脚本示例:
#!/bin/bash
# 增量备份脚本 - 只备份当天修改过的表
DB_PATH="/data/mydb.db"
BACKUP_DIR="/backup/incremental/$(date +%Y%m%d)"
LAST_BACKUP="/backup/last_full.txt"
mkdir -p $BACKUP_DIR
# 获取上次全量备份时间
if [ -f "$LAST_BACKUP" ]; then
LAST_TIME=$(cat $LAST_BACKUP)
else
echo "错误:没有找到全量备份记录,请先执行全量备份"
exit 1
fi
# 遍历所有表,检查修改时间
sqlite3 $DB_PATH ".tables" | tr ' ' '\n' | while read table; do
MOD_TIME=$(sqlite3 $DB_PATH "SELECT max(mtime) FROM (SELECT mtime FROM $table)")
if [ "$MOD_TIME" -gt "$LAST_TIME" ]; then
echo "备份表: $table (修改时间: $MOD_TIME)"
sqlite3 $DB_PATH ".dump $table" > "$BACKUP_DIR/${table}.sql"
fi
done
echo "增量备份完成: $(date)"
备份验证:别等到恢复时才发现文件坏了
嗯,这里我要重点说一下。备份验证是我见过被忽略最多的环节。很多人备份完了就扔在那,等到真要恢复时才发现文件损坏了。
我曾经在一个项目里遇到过:备份脚本跑了半年,文件都好好的,但有一次磁盘坏道导致备份文件损坏。因为没有验证机制,直到系统崩溃要恢复时才发现——那叫一个欲哭无泪。
验证的方法其实很简单:
#!/bin/bash
# 备份验证脚本
BACKUP_FILE=$1
DB_CHECK="/usr/bin/sqlite3"
if [ ! -f "$BACKUP_FILE" ]; then
echo "错误:备份文件不存在"
exit 1
fi
# 1. 检查文件大小
FILE_SIZE=$(stat -c%s "$BACKUP_FILE")
if [ "$FILE_SIZE" -lt 1024 ]; then
echo "警告:备份文件过小,可能不完整"
exit 1
fi
# 2. 使用SQLite的完整性检查
$DB_CHECK "$BACKUP_FILE" "PRAGMA integrity_check;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "错误:数据库完整性检查失败"
exit 1
fi
# 3. 尝试恢复到一个临时位置
TEMP_DB="/tmp/verify_$$.db"
cp "$BACKUP_FILE" "$TEMP_DB"
$DB_CHECK "$TEMP_DB" ".tables" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "验证通过:备份文件可正常读取"
rm -f "$TEMP_DB"
else
echo "错误:备份文件无法读取"
rm -f "$TEMP_DB"
exit 1
fi
异地备份:别把所有鸡蛋放一个篮子里
异地备份说白了就是:本地一份,远程一份。万一机房着火了、硬盘坏了、被人删库了,你还有远程备份可以救命。
在嵌入式系统里,异地备份通常用SCP或RSYNC实现。我个人更推荐RSYNC,因为它支持增量传输,效率更高:
#!/bin/bash
# 异地备份传输脚本
LOCAL_BACKUP="/backup/full/$(date +%Y%m%d)/mydb.db"
REMOTE_HOST="backup@192.168.1.100"
REMOTE_PATH="/remote_backup/device_01/"
# 使用RSYNC传输,支持断点续传
rsync -avz --partial --progress \
-e "ssh -i /root/.ssh/backup_key -o StrictHostKeyChecking=no" \
"$LOCAL_BACKUP" \
"${REMOTE_HOST}:${REMOTE_PATH}"
if [ $? -eq 0 ]; then
echo "异地备份传输成功"
# 传输成功后,在远程执行验证
ssh -i /root/.ssh/backup_key "$REMOTE_HOST" \
"/usr/local/bin/verify_backup.sh ${REMOTE_PATH}mydb.db"
else
echo "异地备份传输失败,请检查网络"
# 这里可以触发告警
fi
完整的备份自动化脚本
把上面这些整合起来,就是一个完整的备份自动化方案。下面是我在实际项目中用过的模板:
#!/bin/bash
# 完整备份自动化脚本
# 用法: ./backup_auto.sh [full|incremental]
MODE=$1
DB_PATH="/data/mydb.db"
BACKUP_ROOT="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/db_backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}
# 全量备份
do_full_backup() {
local BACKUP_DIR="${BACKUP_ROOT}/full/${DATE}"
mkdir -p $BACKUP_DIR
log "开始全量备份..."
sqlite3 $DB_PATH ".backup ${BACKUP_DIR}/mydb.db"
# 记录全量备份时间
echo "$(date +%s)" > "${BACKUP_ROOT}/last_full.txt"
log "全量备份完成: ${BACKUP_DIR}/mydb.db"
}
# 增量备份
do_incremental_backup() {
local BACKUP_DIR="${BACKUP_ROOT}/incremental/${DATE}"
mkdir -p $BACKUP_DIR
log "开始增量备份..."
# 这里调用前面写的增量备份逻辑
# ...
log "增量备份完成"
}
# 验证备份
verify_backup() {
local BACKUP_FILE=$1
log "验证备份文件: $BACKUP_FILE"
sqlite3 $BACKUP_FILE "PRAGMA integrity_check;" 2>/dev/null
if [ $? -eq 0 ]; then
log "验证通过"
return 0
else
log "验证失败"
return 1
fi
}
# 异地传输
remote_transfer() {
local BACKUP_FILE=$1
log "开始异地传输..."
rsync -avz --partial "$BACKUP_FILE" backup@remote:/backup/
if [ $? -eq 0 ]; then
log "异地传输成功"
else
log "异地传输失败"
fi
}
# 主流程
case $MODE in
full)
do_full_backup
BACKUP_FILE="${BACKUP_ROOT}/full/${DATE}/mydb.db"
verify_backup $BACKUP_FILE && remote_transfer $BACKUP_FILE
;;
incremental)
do_incremental_backup
BACKUP_FILE="${BACKUP_ROOT}/incremental/${DATE}/mydb.db"
verify_backup $BACKUP_FILE && remote_transfer $BACKUP_FILE
;;
*)
echo "用法: $0 [full|incremental]"
exit 1
;;
esac
备份策略对比
不同场景适合不同的备份策略,我整理了一个表格供你参考:
| 备份类型 | 空间占用 | 恢复速度 | 适用场景 |
|---|---|---|---|
| 全量备份 | 高 | 快(一次恢复) | 每周一次,作为基础 |
| 增量备份 | 低 | 慢(需叠加全量) | 每日执行,节省空间 |
| 差异备份 | 中 | 中(需叠加全量) | 对恢复速度有要求的场景 |
最后说一句:备份不是做给别人看的,是给自己留后路的。你永远不知道系统什么时候会出问题,但你可以确保出了问题能快速恢复。这就是备份自动化的意义所在。
公众号:蓝海资料掘金营,微信deep3321