本文目录导读:

在Shell脚本中实现回滚策略,核心思路是在执行每个关键步骤前,保存当前状态或可恢复信息,一旦后续步骤失败,就调用预先定义的恢复函数。
主要有以下几种实现方式:
基于函数和状态标记的简单回滚
这是最基础也最实用的方法,适合大多数部署场景。
#!/bin/bash
# 定义颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
# 记录已执行的步骤
executed_steps=()
# 定义回滚函数
rollback() {
local step=$1
echo -e "${RED}[ROLLBACK] 开始回滚步骤 $step...${NC}"
case $step in
1)
echo "回滚步骤1: 删除备份文件"
rm -f /tmp/backup.tar.gz
;;
2)
echo "回滚步骤2: 恢复原文件"
mv /tmp/original_bak.conf /etc/app/config.conf 2>/dev/null
;;
3)
echo "回滚步骤3: 停止新服务,启动旧服务"
systemctl stop app-new 2>/dev/null
systemctl start app-old 2>/dev/null
;;
*)
echo "未知步骤: $step"
;;
esac
}
# 执行步骤并检查
execute_step() {
local step=$1
local desc=$2
shift 2
echo -e "${GREEN}[STEP $step] $desc${NC}"
if "$@"; then
executed_steps+=($step)
echo "步骤 $step 成功"
else
echo -e "${RED}[ERROR] 步骤 $step 失败${NC}"
# 倒序回滚已执行成功的步骤
for ((i=${#executed_steps[@]}-1; i>=0; i--)); do
rollback ${executed_steps[$i]}
done
exit 1
fi
}
# 主流程
main() {
# 步骤1: 备份原文件
execute_step 1 "备份配置文件" cp /etc/app/config.conf /tmp/original_bak.conf
# 步骤2: 部署新配置
execute_step 2 "部署新配置" cp /tmp/new_config.conf /etc/app/config.conf
# 步骤3: 重启服务
execute_step 3 "重启服务" systemctl restart app
echo -e "${GREEN}[SUCCESS] 所有步骤完成${NC}"
}
main
使用 trap 捕获异常
利用 trap 在脚本异常退出时自动执行回滚。
#!/bin/bash
# 临时文件列表
temp_files=()
# 清理函数
cleanup() {
echo "[CLEANUP] 开始清理..."
for file in "${temp_files[@]}"; do
if [ -f "$file" ]; then
echo "删除临时文件: $file"
rm -f "$file"
fi
done
# 如果有需要恢复的服务状态
if [ -n "$OLD_SERVICE_STATUS" ]; then
echo "恢复服务状态..."
systemctl $OLD_SERVICE_STATUS app 2>/dev/null
fi
echo "清理完成"
exit 1
}
# 设置退出时的清理
trap cleanup EXIT ERR INT TERM
# 主逻辑
echo "开始部署..."
# 记录原服务状态
OLD_SERVICE_STATUS=$(systemctl is-active app 2>/dev/null || echo "unknown")
echo "原服务状态: $OLD_SERVICE_STATUS"
# 步骤1: 创建临时备份
temp_files+=("/tmp/app_backup/")
mkdir -p /tmp/app_backup
cp -r /usr/local/app/* /tmp/app_backup/ 2>/dev/null
# 步骤2: 部署新版本
cp -r /tmp/newapp/* /usr/local/app/ || exit 1
# 步骤3: 重启服务
systemctl restart app || exit 1
echo "部署成功!"
# 解除 trap,正常退出
trap - EXIT ERR INT TERM
事务性回滚(保存和恢复)
适用于需要保存多个文件状态的场景。
#!/bin/bash
# 事务日志文件
TRANSACTION_LOG="/tmp/deploy_transaction.log"
STATE_SAVE_DIR="/tmp/deploy_state_$(date +%s)"
# 初始化事务日志
init_transaction() {
mkdir -p "$STATE_SAVE_DIR"
> "$TRANSACTION_LOG"
echo "事务初始化: $STATE_SAVE_DIR"
}
# 记录备份
backup_file() {
local src=$1
local backup_path="$STATE_SAVE_DIR/$(echo "$src" | tr '/' '_')"
if [ -f "$src" ]; then
cp "$src" "$backup_path"
echo "backup:$src:$backup_path" >> "$TRANSACTION_LOG"
echo "备份: $src -> $backup_path"
fi
}
# 记录目录状态
backup_dir_state() {
local dir=$1
local state_file="$STATE_SAVE_DIR/dir_$(echo "$dir" | tr '/' '_').state"
if [ -d "$dir" ]; then
find "$dir" -type f > "$state_file"
echo "dir_state:$dir:$state_file" >> "$TRANSACTION_LOG"
echo "记录目录状态: $dir"
fi
}
# 完整回滚
full_rollback() {
echo "[ROLLBACK] 开始回滚..."
if [ ! -f "$TRANSACTION_LOG" ]; then
echo "没有找到事务日志,无法回滚"
return 1
}
# 从日志读取并执行回滚(逆序)
while IFS=':' read -r action src dest; do
case $action in
backup)
if [ -f "$dest" ]; then
cp "$dest" "$src"
echo "恢复: $dest -> $src"
else
echo "警告: 备份文件不存在: $dest"
fi
;;
restart_service)
systemctl start "$src" 2>/dev/null
echo "启动服务: $src"
;;
esac
done < <(tac "$TRANSACTION_LOG")
# 清理临时文件
rm -rf "$STATE_SAVE_DIR" "$TRANSACTION_LOG"
echo "回滚完成"
}
# 使用示例
deploy_with_rollback() {
# 当脚本退出时自动回滚
trap full_rollback EXIT
init_transaction
echo "=== 开始部署 ==="
# 步骤1: 备份配置文件
backup_file "/etc/app/config.conf"
backup_file "/etc/app/database.yml"
# 步骤2: 备份目录状态
backup_dir_state "/usr/local/app/static"
# 步骤3: 部署新文件
cp /tmp/new_config.conf /etc/app/config.conf || exit 1
# 步骤4: 重启服务
systemctl stop app
backup_before_restart "app"
systemctl start app || {
echo "服务启动失败"
exit 1
}
# 部署成功,取消 trap
trap - EXIT
rm -rf "$STATE_SAVE_DIR" "$TRANSACTION_LOG"
echo "=== 部署成功 ==="
}
# 执行部署
deploy_with_rollback
带版本的增量回滚
适合需要回滚到特定版本的场景。
#!/bin/bash
VERSION_DIR="/opt/app/versions"
CURRENT_LINK="/opt/app/current"
ROLLBACK_MARKER="/tmp/rollback_marker"
# 创建新版本部署
deploy_version() {
local version=$1
local deploy_dir="$VERSION_DIR/$version"
echo "部署版本: $version"
# 创建版本目录
mkdir -p "$deploy_dir"
# 复制新版本文件
cp -r /tmp/app_package/* "$deploy_dir/" || exit 1
# 备份当前版本
if [ -L "$CURRENT_LINK" ]; then
local old_target=$(readlink "$CURRENT_LINK")
if [ -n "$old_target" ]; then
echo "当前版本: $(basename $old_target)"
# 创建回滚点
echo "$old_target" >> "$ROLLBACK_MARKER"
fi
fi
# 更新符号链接
ln -sfn "$deploy_dir" "$CURRENT_LINK"
echo "版本 $version 部署完成"
}
# 回滚到上一版本
rollback_to_previous() {
if [ ! -f "$ROLLBACK_MARKER" ]; then
echo "没有可回滚的版本"
return 1
fi
local previous_version=$(tail -1 "$ROLLBACK_MARKER")
sed -i '$d' "$ROLLBACK_MARKER" # 删除最后一行
if [ -d "$previous_version" ]; then
echo "回滚到版本: $(basename $previous_version)"
ln -sfn "$previous_version" "$CURRENT_LINK"
systemctl restart app
echo "回滚完成"
else
echo "错误: 版本目录不存在: $previous_version"
return 1
fi
}
# 列出版本历史
list_versions() {
echo "可用的版本:"
ls -1 "$VERSION_DIR" 2>/dev/null || echo " 没有版本"
echo -e "\n回滚历史:"
if [ -f "$ROLLBACK_MARKER" ]; then
cat "$ROLLBACK_MARKER"
fi
}
# 使用示例
case "${1:-}" in
deploy)
deploy_version "${2:-v$(date +%Y%m%d%H%M%S)}"
;;
rollback)
rollback_to_previous
;;
status)
list_versions
;;
*)
echo "用法: $0 {deploy|rollback|status} [version]"
exit 1
;;
esac
最佳实践建议
-
保持简单:回滚逻辑不要过于复杂,清晰的 if-else 或 case 语句比魔法函数更可靠。
-
幂等性:回滚操作应该是幂等的,多次执行不会造成额外损害。
-
日志记录:记录每一步的操作,方便调试。
-
测试回滚:在测试环境中先验证回滚逻辑。
-
超时处理:长时间运行的步骤应该设置超时,避免回滚也被阻塞。
-
互斥锁:避免多个部署同时进行导致的混乱。
选择哪种实现方式取决于你的具体需求:简单场景用第一种,需要事务性保证的用第三种,需要版本管理的用第四种。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播