Revert "docker-swarm-review"

This reverts commit 49d2697ef4c5047934a642b0df656c5112d37509.
This commit is contained in:
2026-05-13 15:27:11 +08:00
parent 95a34b2f89
commit da02c6a994
128 changed files with 0 additions and 13062 deletions

View File

@@ -1,869 +0,0 @@
#!/bin/bash
#
# Review Redis 故障切换脚本
# 当生产环境 Redis 故障时,使用此脚本将 Review 环境切换为主服务
#
# 使用方法:
# ./failover-to-review.sh status - 查看当前状态
# ./failover-to-review.sh failover - 执行故障切换(提升 132 为 Master
# ./failover-to-review.sh sentinel - 仅部署 Review Sentinel
# ./failover-to-review.sh full - 完整切换failover + sentinel
# ./failover-to-review.sh rollback - 安全回滚(先同步数据到生产,再切换)
#
# 安全回滚流程:
# 前置条件:【重要】必须先停止或暂停生产 Sentinel防止自动故障转移
# 1. 让 10.56 先作为 132 的从节点同步数据(反向同步)
# 2. 等待数据同步完成
# 3. 将 132 设为只读,防止切换期间数据丢失
# 4. 提升 10.56 为主节点
# 5. 132 重新指向 10.56,恢复级联复制结构
# 6. 重新启动生产 Sentinel
#
# 数据一致性保证:
# - 回滚前:通过反向同步确保故障期间的数据同步到生产
# - 回滚中:将 132 设为只读min-replicas-to-write=99防止数据丢失
# - 回滚后:验证偏移量差异,确保数据完全同步
#
set -e
# ==================== 全局参数 ====================
# -y 或 --yes: 跳过确认提示(危险操作仍需确认)
# --force: 强制执行,跳过所有确认
AUTO_CONFIRM=false
FORCE_MODE=false
# 解析命令行参数
parse_global_args() {
for arg in "$@"; do
case $arg in
-y|--yes)
AUTO_CONFIRM=true
;;
--force)
AUTO_CONFIRM=true
FORCE_MODE=true
;;
esac
done
}
# 确认提示(如果 AUTO_CONFIRM=true 则自动确认)
confirm_prompt() {
local message=$1
local default=${2:-n} # 默认值 n
if [ "$AUTO_CONFIRM" = true ]; then
log_info "自动确认: $message"
return 0
fi
read -p "$message (y/n): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
return 0
fi
return 1
}
# 危险操作确认(即使 AUTO_CONFIRM=true 也需要确认,除非 FORCE_MODE
confirm_dangerous() {
local message=$1
if [ "$FORCE_MODE" = true ]; then
log_warn "强制模式: 跳过危险操作确认"
return 0
fi
read -p "$message (y/n): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
return 0
fi
return 1
}
# ==================== 配置 ====================
REDIS_PASSWORD="sino#650"
SENTINEL_PASSWORD="sino#650"
# Review 节点
NODE_132="192.168.3.132"
NODE_133="192.168.3.133"
NODE_134="192.168.3.134"
# 生产节点
PROD_MASTER="192.168.10.56"
PROD_PORT=6379
REDIS_PORT=6379
SENTINEL_PORT=26379
# 脚本目录
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# ==================== 颜色输出 ====================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# ==================== Docker/Redis 命令封装 ====================
# Redis 镜像(用于临时容器执行命令)
REDIS_IMAGE="bitnami/redis:7.0.11"
# 通过 Docker 容器执行 Redis 命令
# 优先使用已运行的 review_redis_master 容器,否则使用临时容器
redis_cmd() {
local host=$1
local port=$2
shift 2
# 尝试获取运行中的 redis master 容器
local container_id=$(docker ps -q -f name=review_redis_master 2>/dev/null | head -1)
if [ -n "$container_id" ]; then
# 使用已运行的容器
docker exec "$container_id" redis-cli -h "$host" -p "$port" -a "$REDIS_PASSWORD" --no-auth-warning "$@" 2>/dev/null
else
# 使用临时容器执行命令
docker run --rm --network host "$REDIS_IMAGE" redis-cli -h "$host" -p "$port" -a "$REDIS_PASSWORD" --no-auth-warning "$@" 2>/dev/null
fi
}
# 检查 Redis 镜像是否存在,不存在则提示
check_redis_image() {
if ! docker image inspect "$REDIS_IMAGE" &>/dev/null; then
log_warn "Redis 镜像 $REDIS_IMAGE 不存在"
log_info "请先拉取镜像: docker pull $REDIS_IMAGE"
return 1
fi
return 0
}
# ==================== 功能函数 ====================
# 检查节点状态
check_status() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 当前 Redis 状态 ${NC}"
echo -e "${BLUE}========================================${NC}"
for node in $NODE_132 $NODE_133 $NODE_134; do
echo ""
echo -e "${GREEN}[$node]${NC}"
local info=$(redis_cmd "$node" $REDIS_PORT INFO replication 2>/dev/null)
if [ -z "$info" ]; then
echo -e " ${RED}无法连接${NC}"
continue
fi
local role=$(echo "$info" | grep "^role:" | cut -d: -f2 | tr -d '\r\n')
local master_host=$(echo "$info" | grep "^master_host:" | cut -d: -f2 | tr -d '\r\n')
local master_port=$(echo "$info" | grep "^master_port:" | cut -d: -f2 | tr -d '\r\n')
local master_link=$(echo "$info" | grep "^master_link_status:" | cut -d: -f2 | tr -d '\r\n')
local slaves=$(echo "$info" | grep "^connected_slaves:" | cut -d: -f2 | tr -d '\r\n')
echo -e " 角色: ${YELLOW}$role${NC}"
if [ "$role" = "slave" ]; then
echo -e " 主节点: $master_host:$master_port"
echo -e " 同步状态: $master_link"
else
echo -e " 从节点数: $slaves"
fi
done
echo ""
}
# 检查生产环境状态
check_prod_status() {
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 生产环境 Redis 状态 ${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
local info=$(redis_cmd "$PROD_MASTER" $PROD_PORT INFO replication 2>/dev/null)
if [ -z "$info" ]; then
echo -e "${RED}生产环境 $PROD_MASTER:$PROD_PORT 无法连接${NC}"
return 1
fi
local role=$(echo "$info" | grep "^role:" | cut -d: -f2 | tr -d '\r\n')
echo -e "生产主节点 $PROD_MASTER: ${GREEN}$role${NC}"
return 0
}
# 提升 132 为 Master
promote_132() {
echo ""
log_info "正在提升 $NODE_132 为 Master..."
# 执行 SLAVEOF NO ONE
local result=$(redis_cmd "$NODE_132" $REDIS_PORT SLAVEOF NO ONE)
if [ "$result" != "OK" ]; then
log_error "SLAVEOF NO ONE 执行失败: $result"
return 1
fi
sleep 2
# 验证角色
local role=$(redis_cmd "$NODE_132" $REDIS_PORT INFO replication | grep "^role:" | cut -d: -f2 | tr -d '\r\n')
if [ "$role" = "master" ]; then
log_info "$NODE_132 已成功提升为 Master"
else
log_error "$NODE_132 提升失败,当前角色: $role"
return 1
fi
}
# 配置从节点指向新 Master
configure_slaves() {
echo ""
log_info "正在配置从节点指向 $NODE_132..."
for node in $NODE_133 $NODE_134; do
local master_host=$(redis_cmd "$node" $REDIS_PORT INFO replication | grep "^master_host:" | cut -d: -f2 | tr -d '\r\n')
if [ "$master_host" = "$NODE_132" ]; then
log_info "[$node] 已指向 $NODE_132"
else
log_warn "[$node] 当前指向 $master_host,正在重新配置..."
redis_cmd "$node" $REDIS_PORT SLAVEOF "$NODE_132" $REDIS_PORT
sleep 1
# 验证
master_host=$(redis_cmd "$node" $REDIS_PORT INFO replication | grep "^master_host:" | cut -d: -f2 | tr -d '\r\n')
if [ "$master_host" = "$NODE_132" ]; then
log_info "[$node] 配置成功"
else
log_error "[$node] 配置失败"
fi
fi
done
}
# 部署 Sentinel
deploy_sentinel() {
echo ""
log_info "正在部署 Review Sentinel..."
cd "$SCRIPT_DIR"
if [ ! -f "./docker-compose-sentinel.yml" ]; then
log_error "未找到 docker-compose-sentinel.yml"
return 1
fi
if [ ! -f "./env_review" ]; then
log_error "未找到 env_review"
return 1
fi
# 部署
env $(cat ./env_review | xargs) envsubst < ./docker-compose-sentinel.yml | docker stack deploy --compose-file - review_sentinel
log_info "Sentinel 部署命令已执行,等待服务启动..."
sleep 10
# 检查 Sentinel 状态
log_info "检查 Sentinel 状态..."
for node in $NODE_132 $NODE_133 $NODE_134; do
local master=$(redis_cmd "$node" $SENTINEL_PORT SENTINEL get-master-addr-by-name reviewmaster 2>/dev/null)
if [ -n "$master" ]; then
log_info "[$node:$SENTINEL_PORT] Sentinel 运行正常Master: $master"
else
log_warn "[$node:$SENTINEL_PORT] Sentinel 未就绪或未响应"
fi
done
}
# 移除 Sentinel
remove_sentinel() {
echo ""
log_info "正在移除 Review Sentinel..."
docker stack rm review_sentinel 2>/dev/null || true
sleep 5
log_info "Sentinel 已移除"
}
# ==================== 安全回滚相关函数 ====================
# 检查生产节点是否可连接
check_prod_connectable() {
local info=$(redis_cmd "$PROD_MASTER" $PROD_PORT PING 2>/dev/null)
if [ "$info" = "PONG" ]; then
return 0
else
return 1
fi
}
# 获取节点的复制偏移量
get_repl_offset() {
local host=$1
local info=$(redis_cmd "$host" $REDIS_PORT INFO replication 2>/dev/null)
local offset=$(echo "$info" | grep "master_repl_offset:" | cut -d: -f2 | tr -d '\r\n')
if [ -z "$offset" ]; then
offset=$(echo "$info" | grep "slave_repl_offset:" | cut -d: -f2 | tr -d '\r\n')
fi
echo "$offset"
}
# 获取节点角色
get_node_role() {
local host=$1
local info=$(redis_cmd "$host" $REDIS_PORT INFO replication 2>/dev/null)
echo "$info" | grep "^role:" | cut -d: -f2 | tr -d '\r\n'
}
# 获取从节点同步状态
get_slave_sync_status() {
local host=$1
local info=$(redis_cmd "$host" $REDIS_PORT INFO replication 2>/dev/null)
echo "$info" | grep "master_link_status:" | cut -d: -f2 | tr -d '\r\n'
}
# 远程执行生产节点命令(尝试自动执行,失败则提示手动)
exec_prod_redis_cmd() {
local redis_args="$@"
local result=""
# 尝试通过网络直接执行命令
log_info "尝试远程执行 Redis 命令..."
result=$(redis_cmd "$PROD_MASTER" $PROD_PORT $redis_args 2>/dev/null)
if [ -n "$result" ]; then
echo "$result"
return 0
fi
# 无法远程执行,提示手动操作
log_warn "无法远程执行命令,请在生产服务器上手动执行"
return 1
}
# 步骤 1: 让生产 10.56 作为 132 的从节点
rollback_step1_make_prod_slave() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 步骤 1: 配置生产节点为 132 的从节点 ${NC}"
echo -e "${BLUE}========================================${NC}"
# 检查 132 当前是否为 Master
local role_132=$(get_node_role "$NODE_132")
if [ "$role_132" != "master" ]; then
log_error "132 当前不是 Master (角色: $role_132),无法执行回滚"
log_error "请先确认 132 已通过 failover 提升为 Master"
return 1
fi
log_info "确认 132 当前为 Master"
# 检查生产节点是否可连接
if ! check_prod_connectable; then
log_error "生产节点 $PROD_MASTER 无法连接,请先确保生产 Redis 已恢复"
return 1
fi
log_info "生产节点 $PROD_MASTER 可连接"
# 配置 10.56 作为 132 的从节点
log_info "正在配置 $PROD_MASTER 作为 $NODE_132 的从节点..."
# 尝试直接远程执行
local result=$(redis_cmd "$PROD_MASTER" $PROD_PORT SLAVEOF "$NODE_132" $REDIS_PORT 2>/dev/null)
if [ "$result" = "OK" ]; then
log_info "远程执行成功: SLAVEOF $NODE_132 $REDIS_PORT"
else
# 无法远程执行,提示手动操作
echo ""
echo -e "${YELLOW}========================================${NC}"
echo -e "${YELLOW} 无法远程执行,请在生产服务器 ($PROD_MASTER) 上执行: ${NC}"
echo -e "${YELLOW}========================================${NC}"
echo ""
echo "# 方法1: 进入 Redis 容器执行"
echo "docker exec -it \$(docker ps -q -f name=redis) redis-cli -a '$REDIS_PASSWORD' --no-auth-warning SLAVEOF $NODE_132 $REDIS_PORT"
echo ""
echo "# 方法2: 使用临时容器执行"
echo "docker run --rm --network host $REDIS_IMAGE redis-cli -h $PROD_MASTER -p $PROD_PORT -a '$REDIS_PASSWORD' --no-auth-warning SLAVEOF $NODE_132 $REDIS_PORT"
echo ""
if ! confirm_dangerous "已在生产服务器执行上述命令?"; then
log_warn "操作已取消"
return 1
fi
fi
# 验证配置
sleep 2
local prod_role=$(get_node_role "$PROD_MASTER")
local prod_sync=$(get_slave_sync_status "$PROD_MASTER")
if [ "$prod_role" = "slave" ]; then
log_info "生产节点已配置为从节点"
log_info "同步状态: $prod_sync"
if [ "$prod_sync" = "up" ]; then
log_info "数据同步连接正常"
else
log_warn "同步连接状态: $prod_sync,请等待连接建立"
fi
else
log_error "配置失败,生产节点角色: $prod_role"
return 1
fi
}
# 步骤 2: 等待数据同步完成
rollback_step2_wait_sync() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 步骤 2: 等待数据同步完成 ${NC}"
echo -e "${BLUE}========================================${NC}"
local max_wait=300 # 最大等待 5 分钟
local wait_time=0
local check_interval=5
while [ $wait_time -lt $max_wait ]; do
local master_offset=$(get_repl_offset "$NODE_132")
local slave_offset=$(redis_cmd "$PROD_MASTER" $REDIS_PORT INFO replication 2>/dev/null | grep "slave_repl_offset:" | cut -d: -f2 | tr -d '\r\n')
local sync_status=$(get_slave_sync_status "$PROD_MASTER")
echo -e " Master(132) 偏移量: ${GREEN}$master_offset${NC}"
echo -e " Slave(10.56) 偏移量: ${YELLOW}$slave_offset${NC}"
echo -e " 同步状态: $sync_status"
echo -e " 偏移量差异: $((master_offset - slave_offset))"
if [ "$sync_status" != "up" ]; then
log_warn "同步连接未建立,等待中..."
sleep $check_interval
wait_time=$((wait_time + check_interval))
continue
fi
# 检查偏移量差异
local offset_diff=$((master_offset - slave_offset))
if [ $offset_diff -lt 1000 ]; then
log_info "数据同步基本完成(偏移量差异: $offset_diff"
# 停止 Review 写入后再次确认
echo ""
log_warn "建议:在执行下一步前,先停止 Review 应用对 Redis 的写入"
if confirm_prompt "是否已停止写入并继续?"; then
sleep 2
master_offset=$(get_repl_offset "$NODE_132")
slave_offset=$(redis_cmd "$PROD_MASTER" $REDIS_PORT INFO replication 2>/dev/null | grep "slave_repl_offset:" | cut -d: -f2 | tr -d '\r\n')
offset_diff=$((master_offset - slave_offset))
if [ $offset_diff -eq 0 ]; then
log_info "数据完全同步(偏移量差异: 0"
return 0
else
log_info "当前偏移量差异: $offset_diff"
# 偏移量差异小于 100 字节时自动继续
if [ $offset_diff -lt 100 ] || confirm_prompt "偏移量差异 $offset_diff 字节,是否继续执行切换?"; then
return 0
fi
fi
fi
fi
echo "等待同步... ($wait_time/$max_wait 秒)"
sleep $check_interval
wait_time=$((wait_time + check_interval))
echo ""
done
log_error "同步超时,请检查网络和 Redis 状态"
if confirm_dangerous "是否强制继续?"; then
return 0
fi
return 1
}
# 步骤 3: 切换主从关系
rollback_step3_switch_master() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 步骤 3: 切换主从关系 ${NC}"
echo -e "${BLUE}========================================${NC}"
# 3.0 先将 132 设为只读,防止切换期间有新数据写入
log_info "将 132 设为只读模式,防止切换期间数据写入..."
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET min-replicas-to-write 99
log_info "132 已设为只读min-replicas-to-write=99"
# 等待最后的数据同步到 10.56
log_info "等待最后的数据同步..."
sleep 3
# 再次确认偏移量
local master_offset=$(get_repl_offset "$NODE_132")
local slave_offset=$(redis_cmd "$PROD_MASTER" $REDIS_PORT INFO replication 2>/dev/null | grep "slave_repl_offset:" | cut -d: -f2 | tr -d '\r\n')
local offset_diff=$((master_offset - slave_offset))
log_info "最终偏移量差异: $offset_diff"
# 偏移量差异小于 100 字节时自动继续,否则需要确认
if [ $offset_diff -gt 100 ]; then
log_warn "仍有 $offset_diff 字节数据未同步"
if ! confirm_dangerous "是否继续切换?"; then
# 恢复 132 可写
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET min-replicas-to-write 0
log_warn "已取消132 已恢复可写"
return 1
fi
elif [ $offset_diff -gt 0 ]; then
log_info "偏移量差异 $offset_diff 字节(<100自动继续"
fi
# 3.1 提升 10.56 为主节点
log_info "正在提升 $PROD_MASTER 为主节点..."
# 尝试直接远程执行
local result=$(redis_cmd "$PROD_MASTER" $PROD_PORT SLAVEOF NO ONE 2>/dev/null)
if [ "$result" = "OK" ]; then
log_info "远程执行成功: SLAVEOF NO ONE"
else
# 无法远程执行,提示手动操作
echo ""
echo -e "${YELLOW}========================================${NC}"
echo -e "${YELLOW} 无法远程执行,请在生产服务器 ($PROD_MASTER) 上执行: ${NC}"
echo -e "${YELLOW}========================================${NC}"
echo ""
echo "# 方法1: 进入 Redis 容器执行"
echo "docker exec -it \$(docker ps -q -f name=redis) redis-cli -a '$REDIS_PASSWORD' --no-auth-warning SLAVEOF NO ONE"
echo ""
echo "# 方法2: 使用临时容器执行"
echo "docker run --rm --network host $REDIS_IMAGE redis-cli -h $PROD_MASTER -p $PROD_PORT -a '$REDIS_PASSWORD' --no-auth-warning SLAVEOF NO ONE"
echo ""
if ! confirm_dangerous "已执行上述命令?"; then
# 恢复 132 可写
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET min-replicas-to-write 0
log_warn "已取消132 已恢复可写"
return 1
fi
fi
sleep 2
local prod_role=$(get_node_role "$PROD_MASTER")
if [ "$prod_role" = "master" ]; then
log_info "$PROD_MASTER 已提升为 Master"
else
log_error "提升失败,当前角色: $prod_role"
# 恢复 132 可写
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET min-replicas-to-write 0
return 1
fi
# 3.2 配置 132 指向生产(立即执行,减少时间窗口)
log_info "正在配置 $NODE_132 指向 $PROD_MASTER..."
redis_cmd "$NODE_132" $REDIS_PORT SLAVEOF "$PROD_MASTER" $PROD_PORT
# 恢复 132 可写设置(作为从节点后这个设置不影响,但保持配置干净)
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET min-replicas-to-write 0
# 恢复隐藏配置
log_info "恢复 132 的隐藏从节点配置..."
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET replica-announced no
redis_cmd "$NODE_132" $REDIS_PORT CONFIG SET replica-priority 0
sleep 2
local role_132=$(get_node_role "$NODE_132")
local sync_132=$(get_slave_sync_status "$NODE_132")
if [ "$role_132" = "slave" ] && [ "$sync_132" = "up" ]; then
log_info "$NODE_132 已配置为从节点,同步状态: $sync_132"
else
log_warn "$NODE_132 配置完成,角色: $role_132, 同步状态: $sync_132"
fi
# 3.3 配置 133/134 指向 132
log_info "正在配置从节点指向 $NODE_132..."
for node in $NODE_133 $NODE_134; do
redis_cmd "$node" $REDIS_PORT SLAVEOF "$NODE_132" $REDIS_PORT
sleep 1
local role=$(get_node_role "$node")
local sync=$(get_slave_sync_status "$node")
log_info "[$node] 角色: $role, 同步状态: $sync"
done
}
# 安全回滚主函数
safe_rollback() {
echo ""
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ 安全回滚流程 ║${NC}"
echo -e "${RED}║ ║${NC}"
echo -e "${RED}║ 此流程会先将故障期间 132 的新数据同步到生产 10.56${NC}"
echo -e "${RED}║ 确保数据不丢失后再切换回原有架构。 ║${NC}"
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "回滚流程说明:"
echo " 1. 让 10.56 先作为 132 的从节点(反向同步)"
echo " 2. 等待数据完全同步"
echo " 3. 提升 10.56 为主节点132 重新作为隐藏从节点"
echo " 4. 恢复 133/134 级联复制"
echo ""
echo "前置条件:"
echo " - 132 当前是 Master故障切换状态"
echo " - 10.56 已恢复并可连接"
echo -e " - ${YELLOW}【重要】所有应用已切换到 Review Sentinel${NC}"
echo -e " - ${YELLOW}【重要】生产 Sentinel 已停止${NC}"
echo " - 建议先停止 Review 应用的写入"
echo ""
echo -e "${YELLOW}========================================${NC}"
echo -e "${YELLOW} ⚠️ 生产 Sentinel 处理提醒 ${NC}"
echo -e "${YELLOW}========================================${NC}"
echo ""
echo "在执行回滚前,必须先停止生产 Sentinel"
echo ""
echo "原因:"
echo " 1. 回滚步骤1会让 10.56 变成 132 的从节点"
echo " 2. 如果 Sentinel 还在运行,会检测到 master 下线并触发故障转移"
echo " 3. 此外,任何仍通过生产 Sentinel 连接的应用会收到 READONLY 错误"
echo ""
echo -e "${RED}【重要】请确认以下条件:${NC}"
echo " 1. 所有应用都已切换到 Review Sentinel (reviewmaster)"
echo " 2. 生产 Sentinel 已停止"
echo ""
echo "停止生产 Sentinel 命令(在生产服务器执行):"
echo " docker stack rm prod_sentinel"
echo " # 或者根据实际部署名称调整"
echo ""
# 这是危险操作,必须确认(除非 --force
if ! confirm_dangerous "已确认所有应用已切换且生产 Sentinel 已停止?"; then
log_warn "请先完成上述操作后再执行回滚"
return 1
fi
check_prod_status || {
log_error "生产环境不可用,无法执行回滚"
return 1
}
check_status
if ! confirm_prompt "确认开始安全回滚?"; then
echo "已取消"
return 0
fi
# 移除 Review Sentinel
remove_sentinel
# 执行三个步骤
rollback_step1_make_prod_slave || return 1
rollback_step2_wait_sync || return 1
rollback_step3_switch_master || return 1
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} 安全回滚完成 ${NC}"
echo -e "${GREEN}========================================${NC}"
check_status
echo ""
echo "后续操作:"
echo " 1. 重新启动生产 Sentinel如果之前停止了"
echo " 2. 验证生产 Sentinel 是否正常识别 10.56 为主节点"
echo " 3. 修改应用配置,连接回生产 Sentinel"
echo " 4. 确认 132/133/134 对生产 Sentinel 不可见"
}
# 旧的不安全回滚(保留但标记警告)
unsafe_rollback() {
echo ""
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ⚠️ 警告 ⚠️ ║${NC}"
echo -e "${RED}║ ║${NC}"
echo -e "${RED}║ 此操作会直接让 132 重新指向 10.56,可能导致数据丢失! ║${NC}"
echo -e "${RED}║ 故障期间 132 上的所有写入数据将被覆盖! ║${NC}"
echo -e "${RED}║ ║${NC}"
echo -e "${RED}║ 推荐使用: ./failover-to-review.sh rollback ║${NC}"
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
read -p "确认执行不安全回滚(数据可能丢失)? 输入 'YES' 确认: " confirm
if [ "$confirm" != "YES" ]; then
echo "已取消"
return 0
fi
log_warn "正在执行不安全回滚..."
# 先移除 Sentinel
remove_sentinel
# 重新配置 132 指向生产
log_info "配置 $NODE_132 指向生产 $PROD_MASTER..."
redis_cmd "$NODE_132" $REDIS_PORT SLAVEOF "$PROD_MASTER" $PROD_PORT
sleep 2
# 重新配置 133/134 指向 132
for node in $NODE_133 $NODE_134; do
log_info "配置 $node 指向 $NODE_132..."
redis_cmd "$node" $REDIS_PORT SLAVEOF "$NODE_132" $REDIS_PORT
sleep 1
done
log_warn "不安全回滚完成,请检查数据一致性"
}
# 显示最终状态和连接信息
show_final_info() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} 切换完成 ${NC}"
echo -e "${BLUE}========================================${NC}"
check_status
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Review 应用连接配置 ${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "直连模式:"
echo " host: $NODE_132"
echo " port: $REDIS_PORT"
echo " password: $REDIS_PASSWORD"
echo ""
echo "Sentinel 模式 (Spring Boot):"
echo " spring:"
echo " redis:"
echo " sentinel:"
echo " master: reviewmaster"
echo " nodes: $NODE_132:$SENTINEL_PORT,$NODE_133:$SENTINEL_PORT,$NODE_134:$SENTINEL_PORT"
echo " password: $SENTINEL_PASSWORD"
echo " password: $REDIS_PASSWORD"
echo ""
}
# ==================== 主函数 ====================
main() {
# 解析全局参数
parse_global_args "$@"
echo -e "${GREEN}"
echo "╔════════════════════════════════════════════╗"
echo "║ Review Redis 故障切换脚本 ║"
echo "╚════════════════════════════════════════════╝"
echo -e "${NC}"
if [ "$AUTO_CONFIRM" = true ]; then
log_info "已启用自动确认模式 (-y)"
fi
if [ "$FORCE_MODE" = true ]; then
log_warn "已启用强制模式 (--force),将跳过所有确认"
fi
# 对于需要执行 Redis 命令的操作,检查镜像是否存在
case "${1:-help}" in
status|failover|full|rollback|unsafe-rollback)
check_redis_image || exit 1
;;
esac
case "${1:-help}" in
status)
check_prod_status || true
check_status
;;
failover)
check_status
echo ""
if confirm_prompt "确认执行故障切换(提升 132 为 Master?"; then
promote_132
configure_slaves
check_status
else
echo "已取消"
fi
;;
sentinel)
deploy_sentinel
;;
full)
check_prod_status || true
check_status
echo ""
if confirm_prompt "确认执行完整故障切换(包含 Sentinel 部署)?"; then
promote_132
configure_slaves
deploy_sentinel
show_final_info
else
echo "已取消"
fi
;;
rollback)
safe_rollback
;;
unsafe-rollback)
unsafe_rollback
check_status
;;
help|*)
echo "用法: $0 {status|failover|sentinel|full|rollback|unsafe-rollback} [-y|--yes] [--force]"
echo ""
echo "命令说明:"
echo " status - 查看当前 Redis 状态"
echo " failover - 执行故障切换(提升 132 为 Master"
echo " sentinel - 仅部署 Review Sentinel"
echo " full - 完整切换failover + sentinel"
echo " rollback - 安全回滚(先同步数据到生产,再切换)"
echo " unsafe-rollback - 不安全回滚(直接切换,可能丢数据)"
echo ""
echo "可选参数:"
echo " -y, --yes - 自动确认普通提示(危险操作仍需手动确认)"
echo " --force - 强制模式,跳过所有确认(慎用!)"
echo ""
echo "示例:"
echo " $0 full -y # 自动确认执行完整切换"
echo " $0 rollback # 交互式安全回滚"
echo " $0 rollback --force # 强制执行回滚(跳过所有确认)"
echo ""
echo "故障切换流程:"
echo " 1. 执行 ./failover-to-review.sh status 检查状态"
echo " 2. 执行 ./failover-to-review.sh full 完整切换"
echo " 3. 修改应用配置,连接 Review Sentinel"
echo ""
echo "安全恢复流程(推荐):"
echo " 1. 确认生产环境 Redis 已恢复"
echo " 2. 【重要】确认所有应用已切换到 Review Sentinel"
echo " 3. 【重要】停止生产 Sentinel: docker stack rm prod_sentinel"
echo " 4. 建议先停止 Review 应用对 Redis 的写入"
echo " 5. 执行 ./failover-to-review.sh rollback"
echo " - 步骤1: 让 10.56 作为 132 的从节点(反向同步)"
echo " - 步骤2: 等待数据完全同步"
echo " - 步骤3: 将 132 设为只读,提升 10.56 为主节点,恢复原架构"
echo " 6. 重新启动生产 Sentinel"
echo " 7. 修改应用配置,连接生产 Sentinel"
echo ""
echo "注意:"
echo " - 所有 Redis 操作通过 Docker 容器执行,需要 $REDIS_IMAGE 镜像"
echo " - 如果能连接生产 Redis命令会自动远程执行"
echo " - 如果无法连接,会提示手动在生产服务器执行"
echo " - 如果 review_redis_master 容器运行中,优先使用该容器"
echo " - 否则使用临时容器执行命令"
;;
esac
}
main "$@"