420 lines
13 KiB
Bash
420 lines
13 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# MySQL 8.0.24 增量备份恢复脚本
|
|
# ============================================================================
|
|
# 作者: AI Assistant
|
|
# 版本: 1.0.0
|
|
# 说明: 应用增量备份中的 binlog 到已恢复的全量备份
|
|
#
|
|
# 功能特点:
|
|
# - 基于 binlog 应用增量变更
|
|
# - 支持指定时间点恢复 (PITR)
|
|
# - 自动处理备份链
|
|
# - 完善的错误处理
|
|
#
|
|
# 恢复流程:
|
|
# 1. 首先使用 restore_full.sh 恢复全量备份
|
|
# 2. 然后使用本脚本应用增量备份
|
|
#
|
|
# 使用方法:
|
|
# ./restore_incremental.sh [选项] <增量备份目录>
|
|
#
|
|
# 选项:
|
|
# -d, --database <name> 仅恢复指定数据库
|
|
# -t, --stop-datetime <time> 指定恢复到的时间点 (格式: 'YYYY-MM-DD HH:MM:SS')
|
|
# -y, --yes 跳过确认提示
|
|
# -h, --help 显示帮助信息
|
|
#
|
|
# 示例:
|
|
# ./restore_incremental.sh /path/to/incr_backup
|
|
# ./restore_incremental.sh -t '2023-12-20 15:30:00' /path/to/incr_backup
|
|
# ============================================================================
|
|
|
|
set -o pipefail
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 脚本路径和配置加载
|
|
# ----------------------------------------------------------------------------
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# 加载配置文件
|
|
if [[ ! -f "${SCRIPT_DIR}/config.sh" ]]; then
|
|
echo "[ERROR] 配置文件不存在: ${SCRIPT_DIR}/config.sh"
|
|
exit 1
|
|
fi
|
|
source "${SCRIPT_DIR}/config.sh"
|
|
|
|
# 加载公共函数库
|
|
if [[ ! -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
|
echo "[ERROR] 公共函数库不存在: ${SCRIPT_DIR}/lib/common.sh"
|
|
exit 1
|
|
fi
|
|
source "${SCRIPT_DIR}/lib/common.sh"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 全局变量
|
|
# ----------------------------------------------------------------------------
|
|
RESTORE_TIMESTAMP=$(date +"${TIMESTAMP_FORMAT}")
|
|
LOG_FILE="${LOG_DIR}/restore_incremental_${RESTORE_TIMESTAMP}.log"
|
|
LOCK_FILE="${BACKUP_ROOT_DIR}/.restore.lock"
|
|
BACKUP_PATH=""
|
|
SPECIFIED_DATABASE=""
|
|
STOP_DATETIME=""
|
|
SKIP_CONFIRM=false
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 显示帮助信息
|
|
# ----------------------------------------------------------------------------
|
|
show_help() {
|
|
cat << EOF
|
|
MySQL 8.0.24 增量备份恢复脚本
|
|
|
|
使用方法:
|
|
$(basename "$0") [选项] <增量备份目录>
|
|
|
|
选项:
|
|
-d, --database <name> 仅恢复指定数据库
|
|
-t, --stop-datetime <time> 指定恢复到的时间点
|
|
格式: 'YYYY-MM-DD HH:MM:SS'
|
|
-y, --yes 跳过确认提示
|
|
-h, --help 显示此帮助信息
|
|
|
|
示例:
|
|
$(basename "$0") /path/to/incr_backup
|
|
$(basename "$0") -t '2023-12-20 15:30:00' /path/to/incr_backup
|
|
$(basename "$0") -d mydb /path/to/incr_backup
|
|
|
|
恢复流程:
|
|
1. 首先使用 restore_full.sh 恢复全量备份
|
|
2. 然后使用本脚本应用增量备份
|
|
|
|
注意事项:
|
|
- 必须先恢复对应的全量备份
|
|
- 增量备份需要按顺序应用
|
|
- 如果有多个增量备份,需要依次应用
|
|
EOF
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 解析命令行参数
|
|
# ----------------------------------------------------------------------------
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-d|--database)
|
|
shift
|
|
if [[ -z "$1" ]]; then
|
|
die "选项 -d/--database 需要一个参数"
|
|
fi
|
|
SPECIFIED_DATABASE="$1"
|
|
shift
|
|
;;
|
|
-t|--stop-datetime)
|
|
shift
|
|
if [[ -z "$1" ]]; then
|
|
die "选项 -t/--stop-datetime 需要一个参数"
|
|
fi
|
|
STOP_DATETIME="$1"
|
|
shift
|
|
;;
|
|
-y|--yes)
|
|
SKIP_CONFIRM=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-*)
|
|
die "未知选项: $1\n使用 --help 查看帮助"
|
|
;;
|
|
*)
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
BACKUP_PATH="$1"
|
|
else
|
|
die "多余的参数: $1"
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$BACKUP_PATH" ]]; then
|
|
die "请指定增量备份目录\n使用 --help 查看帮助"
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 验证备份目录
|
|
# ----------------------------------------------------------------------------
|
|
validate_backup_dir() {
|
|
log_info "验证增量备份目录: $BACKUP_PATH"
|
|
|
|
if [[ ! -d "$BACKUP_PATH" ]]; then
|
|
die "备份目录不存在: $BACKUP_PATH"
|
|
fi
|
|
|
|
# 检查是否有 binlog 备份文件
|
|
local binlog_files=$(find "$BACKUP_PATH" -name "*binlog*" -o -name "*.sql*" -type f 2>/dev/null | head -n5)
|
|
|
|
if [[ -z "$binlog_files" ]]; then
|
|
die "备份目录中没有找到 binlog 备份文件"
|
|
fi
|
|
|
|
# 检查元数据文件
|
|
if [[ -f "${BACKUP_PATH}/metadata.txt" ]]; then
|
|
log_info "增量备份元数据:"
|
|
cat "${BACKUP_PATH}/metadata.txt" | grep -E "^(备份类型|备份时间|基准备份):" | while read line; do
|
|
log_info " $line"
|
|
done
|
|
fi
|
|
|
|
log_info "备份目录验证通过"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 显示恢复信息并确认
|
|
# ----------------------------------------------------------------------------
|
|
confirm_restore() {
|
|
echo ""
|
|
echo "============================================================"
|
|
echo "警告: 即将应用增量备份"
|
|
echo "============================================================"
|
|
echo ""
|
|
echo "增量备份目录: $BACKUP_PATH"
|
|
echo "目标服务器: ${MYSQL_HOST}:${MYSQL_PORT}"
|
|
echo "MySQL 用户: $MYSQL_USER"
|
|
|
|
if [[ -n "$SPECIFIED_DATABASE" ]]; then
|
|
echo "目标数据库: $SPECIFIED_DATABASE"
|
|
else
|
|
echo "目标范围: 所有数据库"
|
|
fi
|
|
|
|
if [[ -n "$STOP_DATETIME" ]]; then
|
|
echo "恢复时间点: $STOP_DATETIME"
|
|
fi
|
|
|
|
echo ""
|
|
echo "binlog 备份文件:"
|
|
find "$BACKUP_PATH" -type f \( -name "*.sql*" -o -name "*binlog*" \) | while read f; do
|
|
echo " - $(basename "$f") ($(get_file_size "$f"))"
|
|
done
|
|
|
|
echo ""
|
|
echo "警告: 请确保已先恢复对应的全量备份!"
|
|
echo ""
|
|
|
|
if [[ "$SKIP_CONFIRM" != "true" ]]; then
|
|
read -p "确认要继续吗? (输入 'yes' 确认): " confirm
|
|
if [[ "$confirm" != "yes" ]]; then
|
|
log_info "操作已取消"
|
|
exit 0
|
|
fi
|
|
else
|
|
log_warn "跳过确认 (-y 参数)"
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 应用 binlog
|
|
# ----------------------------------------------------------------------------
|
|
apply_binlog() {
|
|
local binlog_file="$1"
|
|
|
|
log_info "应用 binlog: $(basename "$binlog_file")"
|
|
|
|
local mysql_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}"
|
|
if [[ -n "$MYSQL_PASSWORD" ]]; then
|
|
mysql_args="$mysql_args -p${MYSQL_PASSWORD}"
|
|
fi
|
|
|
|
local mysqlbinlog_args=""
|
|
|
|
# 如果指定了数据库
|
|
if [[ -n "$SPECIFIED_DATABASE" ]]; then
|
|
mysqlbinlog_args="$mysqlbinlog_args --database=$SPECIFIED_DATABASE"
|
|
fi
|
|
|
|
# 如果指定了停止时间
|
|
if [[ -n "$STOP_DATETIME" ]]; then
|
|
mysqlbinlog_args="$mysqlbinlog_args --stop-datetime='$STOP_DATETIME'"
|
|
fi
|
|
|
|
# 判断是否需要解压
|
|
if [[ "$binlog_file" == *.gz ]]; then
|
|
log_debug "解压并应用 binlog..."
|
|
if ! gunzip -c "$binlog_file" | $MYSQL_PATH $mysql_args 2>> "$LOG_FILE"; then
|
|
die "应用 binlog 失败: $binlog_file"
|
|
fi
|
|
elif [[ "$binlog_file" == *.lz4 ]]; then
|
|
log_debug "解压并应用 binlog..."
|
|
if ! lz4 -d -c "$binlog_file" | $MYSQL_PATH $mysql_args 2>> "$LOG_FILE"; then
|
|
die "应用 binlog 失败: $binlog_file"
|
|
fi
|
|
else
|
|
# binlog 文件可能是 mysqlbinlog 输出格式,直接源入
|
|
log_debug "直接应用 binlog..."
|
|
if ! $MYSQL_PATH $mysql_args < "$binlog_file" 2>> "$LOG_FILE"; then
|
|
die "应用 binlog 失败: $binlog_file"
|
|
fi
|
|
fi
|
|
|
|
log_info "binlog 应用成功"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 应用所有增量备份
|
|
# ----------------------------------------------------------------------------
|
|
apply_all_incrementals() {
|
|
log_info "开始应用增量备份..."
|
|
|
|
# 获取所有 binlog 备份文件并按名称排序
|
|
local binlog_files=$(find "$BACKUP_PATH" -type f \( -name "*.sql*" -o -name "*-bin.*" \) 2>/dev/null | sort)
|
|
|
|
if [[ -z "$binlog_files" ]]; then
|
|
log_warn "没有找到需要应用的 binlog 文件"
|
|
return 0
|
|
fi
|
|
|
|
local count=0
|
|
local total=$(echo "$binlog_files" | wc -l)
|
|
|
|
while IFS= read -r binlog_file; do
|
|
((count++))
|
|
log_info "处理进度: $count/$total"
|
|
apply_binlog "$binlog_file"
|
|
done <<< "$binlog_files"
|
|
|
|
log_info "共应用 $count 个 binlog 文件"
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 处理增量备份链
|
|
# ----------------------------------------------------------------------------
|
|
process_incremental_chain() {
|
|
log_info "检查增量备份链..."
|
|
|
|
# 检查是否有基准备份路径记录
|
|
local base_path_file="${BACKUP_PATH}/base_backup_path.txt"
|
|
|
|
if [[ -f "$base_path_file" ]]; then
|
|
local base_backup=$(cat "$base_path_file")
|
|
log_info "基准备份: $base_backup"
|
|
|
|
# 检查基准备份是否为增量备份
|
|
if [[ "$base_backup" == *"incr"* ]]; then
|
|
log_warn "发现增量备份链,需要先应用先前的增量备份"
|
|
log_warn "基准增量备份: $base_backup"
|
|
echo ""
|
|
echo "建议的恢复顺序:"
|
|
echo "1. 首先恢复全量备份"
|
|
echo "2. 依次应用增量备份链"
|
|
echo ""
|
|
|
|
if [[ "$SKIP_CONFIRM" != "true" ]]; then
|
|
read -p "是否已按顺序恢复? (输入 'yes' 继续): " confirm
|
|
if [[ "$confirm" != "yes" ]]; then
|
|
log_info "操作已取消"
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 初始化恢复环境
|
|
# ----------------------------------------------------------------------------
|
|
init_restore() {
|
|
log_info "============================================================"
|
|
log_info "MySQL 增量备份恢复开始"
|
|
log_info "============================================================"
|
|
log_info "恢复时间: $RESTORE_TIMESTAMP"
|
|
log_info "备份目录: $BACKUP_PATH"
|
|
log_info "日志文件: $LOG_FILE"
|
|
|
|
if [[ -n "$STOP_DATETIME" ]]; then
|
|
log_info "恢复时间点: $STOP_DATETIME"
|
|
fi
|
|
|
|
log_info "============================================================"
|
|
|
|
# 设置错误处理
|
|
setup_error_trap
|
|
|
|
# 检查必要命令
|
|
check_commands "$MYSQL_PATH" "$MYSQLBINLOG_PATH" "gunzip"
|
|
|
|
# 检查 MySQL 连接
|
|
check_mysql_connection
|
|
|
|
# 创建必要目录
|
|
ensure_dir "$LOG_DIR"
|
|
|
|
# 获取锁
|
|
acquire_lock "$LOCK_FILE" "恢复"
|
|
|
|
# 设置清理 trap
|
|
trap cleanup_restore EXIT
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 清理函数
|
|
# ----------------------------------------------------------------------------
|
|
cleanup_restore() {
|
|
local exit_code=$?
|
|
|
|
# 释放锁
|
|
release_lock "$LOCK_FILE"
|
|
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
log_error "恢复过程中发生错误,退出码: $exit_code"
|
|
send_notification "[失败] MySQL 增量恢复" "恢复失败,请检查日志: $LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 主函数
|
|
# ----------------------------------------------------------------------------
|
|
main() {
|
|
# 记录开始时间
|
|
START_TIME=$(date +%s)
|
|
|
|
# 解析参数
|
|
parse_args "$@"
|
|
|
|
# 初始化
|
|
init_restore
|
|
|
|
# 验证备份目录
|
|
validate_backup_dir
|
|
|
|
# 确认恢复操作
|
|
confirm_restore
|
|
|
|
# 处理增量备份链
|
|
process_incremental_chain
|
|
|
|
# 应用增量备份
|
|
apply_all_incrementals
|
|
|
|
# 计算总耗时
|
|
local end_time=$(date +%s)
|
|
local duration=$(calculate_duration "$START_TIME" "$end_time")
|
|
|
|
log_info "============================================================"
|
|
log_info "MySQL 增量备份恢复完成"
|
|
log_info "============================================================"
|
|
log_info "执行耗时: $duration"
|
|
log_info "============================================================"
|
|
|
|
# 发送成功通知
|
|
send_notification "[成功] MySQL 增量恢复" "恢复完成\n耗时: $duration"
|
|
|
|
return 0
|
|
}
|
|
|
|
# 执行主函数
|
|
main "$@"
|