Shell脚本如何实现跳过回滚策略

wen 实用脚本 25

本文目录导读:

Shell脚本如何实现跳过回滚策略

  1. 版本号控制策略
  2. 状态文件标记法
  3. 数据库迁移回滚策略
  4. 配置文件驱动策略
  5. 最佳实践建议
  6. 完整示例:部署脚本

Shell脚本实现跳过回滚策略通常用于自动化部署、数据库迁移或任务执行中,以下提供几种常见实现方式:

版本号控制策略

#!/bin/bash
# 版本文件存储位置
VERSION_FILE="/tmp/deploy_version.txt"
CURRENT_VERSION=$(cat $VERSION_FILE 2>/dev/null || echo "0")
# 定义执行函数,带版本标记
execute_step() {
    local step_name="$1"
    local step_version="$2"
    local step_command="$3"
    # 跳过小于当前版本的步骤
    if [ "$step_version" -le "$CURRENT_VERSION" ]; then
        echo "[SKIP] $step_name (version $step_version <= current $CURRENT_VERSION)"
        return 0
    fi
    echo "[EXECUTE] $step_name (version $step_version)"
    eval "$step_command"
    if [ $? -eq 0 ]; then
        echo "$step_version" > "$VERSION_FILE"
        echo "[SUCCESS] $step_name completed"
    else
        echo "[FAILED] $step_name - rolling back to version $CURRENT_VERSION"
        echo "$CURRENT_VERSION" > "$VERSION_FILE"  # 回滚版本号
        return 1
    fi
}
# 使用示例
execute_step "备份数据库" 1 "echo 'Backup completed'"
execute_step "更新配置" 2 "echo 'Config updated'"
execute_step "重启服务" 3 "echo 'Service restarted'"

状态文件标记法

#!/bin/bash
STATUS_DIR="/tmp/deploy_status"
mkdir -p "$STATUS_DIR"
# 执行和跳过逻辑
run_or_skip() {
    local step_id="$1"
    local step_name="$2"
    local step_func="$3"
    # 检查是否已成功执行
    if [ -f "$STATUS_DIR/$step_id.completed" ]; then
        echo "[SKIP] $step_name - already completed"
        return 0
    fi
    # 检查是否有回滚标记
    if [ -f "$STATUS_DIR/$step_id.rollback" ]; then
        echo "[ROLLBACK] $step_name - undoing changes"
        if [ -f "$STATUS_DIR/$step_id.rollback_script" ]; then
            source "$STATUS_DIR/$step_id.rollback_script"
            rm -f "$STATUS_DIR/$step_id.rollback"
            rm -f "$STATUS_DIR/$step_id.completed"
        fi
        return 0
    fi
    # 执行步骤
    echo "[EXECUTE] $step_name"
    if $step_func; then
        touch "$STATUS_DIR/$step_id.completed"
        return 0
    else
        echo "[FAIL] $step_name - creating rollback marker"
        touch "$STATUS_DIR/$step_id.rollback"
        return 1
    fi
}
# 示例函数
step1_backup() {
    echo "Performing backup..."
    return 0
}
step2_update() {
    echo "Updating application..."
    # 模拟失败
    if [ "$RANDOM" -gt 10000 ]; then
        return 1
    fi
    return 0
}
# 执行流程
run_or_skip "001" "Backup Database" step1_backup
run_or_skip "002" "Update Application" step2_update

数据库迁移回滚策略

#!/bin/bash
DB_NAME="myapp"
MIGRATIONS_DIR="/opt/migrations"
# 记录已执行的迁移
get_executed_migrations() {
    mysql -e "SELECT migration_name FROM schema_migrations ORDER BY id;" "$DB_NAME" 2>/dev/null
}
# 记录迁移执行状态
mark_migration_executed() {
    local name="$1"
    mysql -e "INSERT INTO schema_migrations (migration_name, executed_at) VALUES ('$name', NOW());" "$DB_NAME"
}
# 带跳过回滚的迁移执行
execute_migration() {
    local migration_file="$1"
    local migration_name=$(basename "$migration_file" .sql)
    # 检查是否已执行
    if get_executed_migrations | grep -q "$migration_name"; then
        echo "[SKIP] $migration_name already executed"
        return 0
    fi
    echo "[EXECUTE] Migration: $migration_name"
    # 执行迁移(捕获错误)
    if mysql "$DB_NAME" < "$migration_file" 2>/tmp/migration_error; then
        mark_migration_executed "$migration_name"
        echo "[SUCCESS] $migration_name executed"
    else
        echo "[FAILED] Migration failed: $(cat /tmp/migration_error)"
        # 自动回滚(如果有对应的回滚文件)
        local rollback_file="${migration_file%.sql}_rollback.sql"
        if [ -f "$rollback_file" ]; then
            echo "[ROLLBACK] Executing rollback..."
            mysql "$DB_NAME" < "$rollback_file"
        fi
        return 1
    fi
}
# 执行所有待处理的迁移
for migration in $(ls $MIGRATIONS_DIR/*.sql | sort); do
    execute_migration "$migration" || break
done

配置文件驱动策略

#!/bin/bash
# config.yaml 格式示例
: '
steps:
  - name: "备份"
    id: backup
    command: "echo backup"
    rollback: "echo restore_backup"
  - name: "更新"
    id: update
    command: "echo update"
    rollback: "echo revert_update"
'
# 读取配置并执行
execute_with_skip_rollback() {
    local config_file="$1"
    local state_file="${2:-/tmp/deploy_state.txt}"
    # 读取已完成的步骤
    declare -A completed_steps
    if [ -f "$state_file" ]; then
        while IFS= read -r line; do
            completed_steps["$line"]=1
        done < "$state_file"
    fi
    # 使用 yq 解析 YAML (需要安装 yq)
    local steps_count=$(yq e '.steps | length' "$config_file")
    for ((i=0; i<steps_count; i++)); do
        local step_name=$(yq e ".steps[$i].name" "$config_file")
        local step_id=$(yq e ".steps[$i].id" "$config_file")
        local step_cmd=$(yq e ".steps[$i].command" "$config_file")
        local rollback_cmd=$(yq e ".steps[$i].rollback" "$config_file")
        # 跳过已完成的步骤
        if [ "${completed_steps[$step_id]}" = "1" ]; then
            echo "[SKIP] $step_name already completed"
            continue
        fi
        # 执行步骤
        echo "[EXECUTE] $step_name"
        if eval "$step_cmd"; then
            echo "$step_id" >> "$state_file"
            echo "[SUCCESS] $step_name"
        else
            echo "[FAILED] $step_name - executing rollback"
            eval "$rollback_cmd" || echo "[WARN] Rollback failed for $step_name"
            return 1
        fi
    done
}
# 使用
execute_with_skip_rollback "deploy_config.yaml"

最佳实践建议

状态存储

# 使用数据库记录状态
mysql -e "
CREATE TABLE IF NOT EXISTS deploy_steps (
    id INT AUTO_INCREMENT PRIMARY KEY,
    step_name VARCHAR(255),
    status ENUM('completed', 'failed', 'rolled_back'),
    executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"

异常处理

# 设置错误处理
set -eE
trap 'handle_error $LINENO' ERR
handle_error() {
    local line=$1
    echo "Error occurred at line $line"
    # 执行全局回滚逻辑
}

幂等性设计

# 确保操作可以安全重复执行
safe_mkdir() {
    local dir="$1"
    if [ ! -d "$dir" ]; then
        mkdir -p "$dir"
    fi
}
safe_symlink() {
    local target="$1"
    local link="$2"
    if [ -L "$link" ] && [ "$(readlink "$link")" = "$target" ]; then
        return 0  # 已存在且指向正确
    fi
    ln -sf "$target" "$link"
}

完整示例:部署脚本

#!/bin/bash
# 配置
DEPLOY_DIR="/opt/app"
BACKUP_DIR="/opt/backups"
STATE_FILE="/tmp/deploy_state.txt"
# 初始化状态
mkdir -p "$(dirname "$STATE_FILE")"
# 检查并跳过已完成的步骤
check_step() {
    local step="$1"
    if grep -q "^$step$" "$STATE_FILE" 2>/dev/null; then
        echo "[SKIP] $step"
        return 0
    fi
    return 1
}
# 标记步骤完成
mark_complete() {
    local step="$1"
    echo "$step" >> "$STATE_FILE"
}
# 带回滚的执行
execute_step() {
    local step="$1"
    local action="$2"
    local rollback="$3"
    check_step "$step" && return 0
    echo "[EXECUTE] $step"
    if eval "$action"; then
        mark_complete "$step"
    else
        echo "[FAIL] Rolling back: $step"
        eval "$rollback" || echo "[WARN] Rollback failed"
        return 1
    fi
}
# 部署步骤
execute_step "backup" \
    "cp -r $DEPLOY_DIR $BACKUP_DIR/backup_$(date +%Y%m%d)" \
    "rm -rf $BACKUP_DIR/backup_$(date +%Y%m%d)"
execute_step "stop_service" \
    "systemctl stop myapp" \
    "systemctl start myapp"
execute_step "update_files" \
    "cp -r /tmp/new_version/* $DEPLOY_DIR/" \
    "cp -r $BACKUP_DIR/backup_$(date +%Y%m%d)/* $DEPLOY_DIR/"
execute_step "start_service" \
    "systemctl start myapp" \
    "systemctl stop myapp"
echo "Deployment completed successfully"

这些策略可以根据实际需求组合使用,关键是要确保操作的幂等性可恢复性

抱歉,评论功能暂时关闭!