Redis 分布式集群与主从复制的一键部署脚本:从繁琐到高效的自动化方案

Redis 分布式集群与主从复制的一键部署脚本

引言

在分布式系统中,Redis 集群因其高可用性和可扩展性成为缓存服务的首选。然而,手动部署多节点 Redis 集群往往涉及复杂的配置步骤,容易出错且效率低下。

本文介绍的 Redis 集群一键部署脚本,通过自动化流程实现环境检测、安装、配置及集群初始化,大幅简化部署流程,将传统 4 小时部署流程缩短至 15 分钟,适用于开发测试及中小型生产环境。

部署前的技术准备

系统要求

  • Linux 系统(推荐 CentOS 7+),需具备 root 权限
  • 至少 4GB 内存(根据集群规模调整)
  • 已安装 wgetmakegcc 等编译工具

配置参数说明(脚本开头部分)

基础配置
ini 复制代码
readonly IP=$(hostname -I | awk '{print $1}')          # 自动获取主机IP地址,用于节点间通信
readonly REDIS_INSTALL_DIR="/usr/local/redis_cluster"  # Redis源码编译安装目录
readonly REQUIRED_VERSION="5.0.6"                      # 指定安装的Redis版本(5.0.6为稳定版本)
readonly REDIS_DBS_PATH="/data/redis_dbs"              # Redis数据文件存储目录,建议挂载高性能磁盘
readonly REDIS_CATALOG_PATH="/etc/redis"               # Redis配置文件存储目录
readonly REDIS_EXECUTABLE="redis-server"               # Redis服务可执行文件名
readonly LOG_FILE="/var/log/redis_install.log"         # 部署日志文件路径
集群架构配置

端口分配规则:

  • 每组集群包含3个主节点(6479/6480/6481)和3个从节点(7479/7480/7481)
  • 主从节点按顺序配对:6479→7479, 6480→7480, 6481→7481
  • 多组集群使用不同端口范围,避免端口冲突
yaml 复制代码
readonly CLUSTER_PORTS=(6479 6480 6481 7479 7480 7481 6779 6780 6781 7779 7780 7781 6879 6880 6881 7879 7880 7881) # 集群端口配置(3组集群,每组6个节点,共18个节点)
readonly CLUSTER_GROUPS=3                               # 集群组数,每组独立管理16384个哈希槽
readonly CLUSTER_MASTERS=3                              # 每组集群中的主节点数量
readonly CLUSTER_NODES_PER_GROUP=6                      # 每组集群中的总节点数(主节点+从节点)
单机主从配置

端口分配规则:

  • 每组包含1个主节点和1个从节点
  • 主从节点按顺序配对:6579→7579, 6679→7679
  • 适用于不需要分片但需要高可用的场景(如Session缓存)
ini 复制代码
readonly STANDALONE_PORTS=(6579 7579 6679 7679) # 单机主从端口配置(2组主从,每组2个节点,共4个节点)
readonly STANDALONE_PAIRS=2 # 单机主从组数
readonly STANDALONE_MASTERS=1 # 每组主从复制中的主节点数量

一键部署步骤

执行脚本

bash 复制代码
chmod +x redis_install.sh 
./redis_install.sh
# 部署日志查看
tail -f /var/log/redis_install.log

执行过程解析

脚本按以下流程自动执行(可通过日志文件 var/log/redis_install.log 查看详情):

service统一管控

bash 复制代码
service redis start # 启动所有节点 
service redis restart # 重新启动所有节点
service redis status # 查看运行状态 
service redis stop # 停止所有节点

混合部署架构拓扑图

完整部署脚本

bash 复制代码
####################################################
# 文件名:redis_install.sh
# 描述:Redis分布式集群与主从复制一键部署脚本
# 架构:支持混合架构, 多组Cluster集群 + 多组单机主从
# 环境:适用于CentOS 7+/Ubuntu 18.04+, 需root权限执行
####################################################
#!/bin/bash
# ==================== 全局配置 ====================
# 基础配置
readonly IP=$(hostname -I | awk '{print $1}')          # 自动获取主机IP地址,用于节点间通信
readonly REDIS_INSTALL_DIR="/usr/local/redis_cluster"  # Redis源码编译安装目录
readonly REQUIRED_VERSION="5.0.6"                      # 指定安装的Redis版本(5.0.6为稳定版本)
readonly REDIS_DBS_PATH="/data/redis_dbs"              # Redis数据文件存储目录,建议挂载高性能磁盘
readonly REDIS_CATALOG_PATH="/etc/redis"               # Redis配置文件存储目录
readonly REDIS_EXECUTABLE="redis-server"               # Redis服务可执行文件名
readonly LOG_FILE="/var/log/redis_install.log"         # 部署日志文件路径

# 集群架构配置
readonly CLUSTER_PORTS=(6479 6480 6481 7479 7480 7481 6779 6780 6781 7779 7780 7781 6879 6880 6881 7879 7880 7881) # 集群端口配置(3组集群,每组6个节点,共18个节点)
readonly CLUSTER_GROUPS=3                               # 集群组数,每组独立管理16384个哈希槽
readonly CLUSTER_MASTERS=3                              # 每组集群中的主节点数量
readonly CLUSTER_NODES_PER_GROUP=6                      # 每组集群中的总节点数(主节点+从节点)

# 单机主从配置
readonly STANDALONE_PORTS=(6579 7579 6679 7679) # 单机主从端口配置(2组主从,每组2个节点,共4个节点)
readonly STANDALONE_PAIRS=2 # 单机主从组数
readonly STANDALONE_MASTERS=1 # 每组主从复制中的主节点数量

# ==================== 初始化函数 ====================

init_logging() {
    exec > >(tee -a "$LOG_FILE") 2>&1
    echo -e "\n=== Redis部署日志 $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$LOG_FILE"
}

log() {
    local level=$1
    local message=$2
    echo -e "\n[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message"
}

show_progress() {
    local current=$1 total=$2 message=$3
    local width=50 percent=$((current*100/total))
    printf "\r[%-${width}s] %d%% %s" "$(printf '#%.0s' $(seq 1 $((current*width/total))))" "$percent" "$message"
    [ $current -eq $total ] && echo
}

validate_config() {
    # 验证集群配置
    local total_ports=$((CLUSTER_GROUPS * CLUSTER_NODES_PER_GROUP))
    if [ ${#CLUSTER_PORTS[@]} -ne $total_ports ]; then
        log "ERROR" "集群端口数不匹配: 需要$total_ports个端口,实际配置了${#CLUSTER_PORTS[@]}个"
        exit 1
    fi

    # 验证端口是否被占用
    for port in "${CLUSTER_PORTS[@]}" "${STANDALONE_PORTS[@]}"; do
        if ss -tuln | grep -q ":$port "; then
            log "ERROR" "端口 $port 已被占用"
            exit 1
        fi
    done
}

clean_environment() {
    log "INFO" "开始清理部署环境..."
    
    # 检查Redis进程是否存在
    if pgrep -x "$REDIS_EXECUTABLE" > /dev/null; then
        log "INFO" "检测到Redis进程正在运行,准备优雅终止..."
        if pkill -INT "$REDIS_EXECUTABLE"; then
            log "INFO" "已发送终止信号,等待Redis进程退出..."
            sleep 3  # 等待进程优雅退出
            if pgrep -x "$REDIS_EXECUTABLE" > /dev/null; then
                log "WARN" "Redis进程未正常退出,尝试强制终止..."
                pkill -9 "$REDIS_EXECUTABLE"
            fi
            log "INFO" "Redis进程已终止"
        else
            log "ERROR" "终止Redis进程失败,请手动检查"
            return 1
        fi
    else
        log "INFO" "未检测到Redis进程运行"
    fi
    
    # 清理数据目录
    log "INFO" "清理Redis数据文件..."
    if [ -d "$REDIS_DBS_PATH" ]; then
        if rm -rf "$REDIS_DBS_PATH"/*; then
            log "INFO" "数据文件清理完成: $REDIS_DBS_PATH"
        else
            log "ERROR" "数据文件清理失败,可能权限不足"
            return 1
        fi
    else
        log "INFO" "数据目录不存在,跳过清理: $REDIS_DBS_PATH"
    fi
    
    # 清理配置目录
    log "INFO" "清理Redis配置文件..."
    if [ -d "$REDIS_CATALOG_PATH" ]; then
        if rm -rf "$REDIS_CATALOG_PATH"/*; then
            log "INFO" "配置文件清理完成: $REDIS_CATALOG_PATH"
        else
            log "ERROR" "配置文件清理失败,可能权限不足"
            return 1
        fi
    else
        log "INFO" "配置目录不存在,跳过清理: $REDIS_CATALOG_PATH"
    fi
    
    log "INFO" "环境清理完成"
    return 0
}

# ==================== Redis安装 ====================

install_redis() {
    log "INFO" "开始安装Redis $REQUIRED_VERSION"
    
    mkdir -p "$REDIS_INSTALL_DIR" || return 1
    cd "$REDIS_INSTALL_DIR" || return 1
    
    local pkg="redis-$REQUIRED_VERSION.tar.gz"
    log "INFO" "下载Redis安装包..."
    if ! wget -q "https://download.redis.io/releases/$pkg"; then
        log "ERROR" "下载失败"
        return 1
    fi
    
    log "INFO" "解压安装包..."
    tar xzf "$pkg" || return 1
    cd "redis-$REQUIRED_VERSION" || return 1
    
    log "INFO" "编译安装..."
    make -j$(nproc) >/dev/null 2>&1 && make install >/dev/null 2>&1 || {
        log "ERROR" "编译失败"
        return 1
    }
    
    yum remove -y redis >/dev/null 2>&1
    REDIS_CONF_DEFAULT="$REDIS_INSTALL_DIR/redis-$REQUIRED_VERSION/redis.conf"
    log "INFO" "Redis安装成功"
}

check_redis() {
    if command -v "$REDIS_EXECUTABLE" &> /dev/null; then
        local version=$("$REDIS_EXECUTABLE" --version | awk '{print $3}' | cut -d= -f2)
        if [ "$version" == "$REQUIRED_VERSION" ]; then
            log "INFO" "Redis $REQUIRED_VERSION 已安装"
            REDIS_CONF_DEFAULT="$REDIS_INSTALL_DIR/redis-$version/redis.conf"
            return 0
        else
            log "WARN" "现有版本($version)与需求版本($REQUIRED_VERSION)不同"
            return 1
        fi
    fi
    return 1
}

# ==================== 配置处理 ====================

safe_config_update() {
    local file=$1
    local pattern=$2
    local replacement=$3
    local backup="${file}.bak"
    
    # 创建备份
    cp -f "$file" "$backup" || return 1
    
    # 执行修改
    if ! sed -i "$pattern" "$file"; then
        log "ERROR" "修改配置文件失败: $file"
        mv -f "$backup" "$file"
        return 1
    fi
    
    # 验证修改
    if ! grep -q "$replacement" "$file"; then
        log "ERROR" "修改未生效: $file"
        mv -f "$backup" "$file"
        return 1
    fi
    
    rm -f "$backup"
    return 0
}

configure_cluster_nodes() {
    local port=$1
    local conf="$REDIS_CATALOG_PATH/${port}.conf"
    
    log "INFO" "配置集群节点: $port"
    
    # 创建配置文件目录
    mkdir -p "$REDIS_CATALOG_PATH" || return 1
    cp "$REDIS_CONF_DEFAULT" "$conf" || return 1

    # 使用安全的配置更新方式
    safe_config_update "$conf" '/bind 127.0.0.1/s/bind 127.0.0.1/#bind 127.0.0.1/g' "#bind 127.0.0.1" || return 1
    safe_config_update "$conf" "/6379/s/6379/$port/g" "port $port" || return 1
    safe_config_update "$conf" '/protected-mode yes/s/yes/no/g' "protected-mode no" || return 1
    safe_config_update "$conf" "/dbfilename dump.rdb/s/dump.rdb/dump-$port.rdb/g" "dbfilename dump-$port.rdb" || return 1
    
    # 处理dir配置
    local dir_line=$(grep -n "dir ./" "$conf" | cut -d: -f1)
    [ -z "$dir_line" ] && { log "ERROR" "找不到dir配置"; return 1; }
    safe_config_update "$conf" "${dir_line}s|dir .*|dir $REDIS_DBS_PATH|g" "dir $REDIS_DBS_PATH" || return 1
    
    # 集群配置
    safe_config_update "$conf" '/# cluster-enabled/s/# cluster-enabled/cluster-enabled/g' "cluster-enabled yes" || return 1
    safe_config_update "$conf" '/# cluster-config-file/s/# cluster-config-file/cluster-config-file/g' "cluster-config-file" || return 1
    safe_config_update "$conf" '/# cluster-node-timeout/s/# cluster-node-timeout/cluster-node-timeout/g' "cluster-node-timeout" || return 1
    safe_config_update "$conf" '/daemonize no/s/no/yes/g' "daemonize yes" || return 1
    
    log "INFO" "集群节点配置完成: $port"
}

configure_standalone_nodes() {
    local port=$1 master_port=$2 index=$3
    local conf="$REDIS_CATALOG_PATH/${port}.conf"
    
    log "INFO" "配置单机节点: $port"
    
    mkdir -p "$REDIS_CATALOG_PATH" || return 1
    cp "$REDIS_CONF_DEFAULT" "$conf" || return 1

    safe_config_update "$conf" '/bind 127.0.0.1/s/bind 127.0.0.1/#bind 127.0.0.1/g' "#bind 127.0.0.1" || return 1
    safe_config_update "$conf" "/6379/s/6379/$port/g" "port $port" || return 1
    safe_config_update "$conf" '/protected-mode yes/s/yes/no/g' "protected-mode no" || return 1
    
    case $index in
        0) safe_config_update "$conf" "/dbfilename dump.rdb/s/dump.rdb/dump-cache.rdb/g" "dbfilename dump-cache.rdb" || return 1 ;;
        1) 
            safe_config_update "$conf" "/dbfilename dump.rdb/s/dump.rdb/dump-cache-readonly.rdb/g" "dbfilename dump-cache-readonly.rdb" || return 1
            echo "slaveof $IP $master_port" >> "$conf"
            ;;
        2) safe_config_update "$conf" "/dbfilename dump.rdb/s/dump.rdb/dump-online.rdb/g" "dbfilename dump-online.rdb" || return 1 ;;
        3) 
            safe_config_update "$conf" "/dbfilename dump.rdb/s/dump.rdb/dump-online-readonly.rdb/g" "dbfilename dump-online-readonly.rdb" || return 1
            echo "slaveof $IP $master_port" >> "$conf"
            ;;
    esac
    
    local dir_line=$(grep -n "dir ./" "$conf" | cut -d: -f1)
    [ -z "$dir_line" ] && { log "ERROR" "找不到dir配置"; return 1; }
    safe_config_update "$conf" "${dir_line}s|dir .*|dir $REDIS_DBS_PATH|g" "dir $REDIS_DBS_PATH" || return 1
    safe_config_update "$conf" '/daemonize no/s/no/yes/g' "daemonize yes" || return 1
    
    log "INFO" "单机节点配置完成: $port"
}

generate_management_script() {
    log "INFO" "开始生成Redis管理脚本"
    
    # 生成Redis启动脚本内容
    cat > /etc/init.d/redis << 'EOF'
#!/bin/bash
# Redis管理脚本 - 支持启动/停止/重启/状态检查所有Redis实例

# 配置常量
EXEC="/usr/local/bin/redis-server"
CLIEXEC="/usr/local/bin/redis-cli"
CONFIG_DIR="/etc/redis"
RUN_DIR="/var/run"

# 确保运行目录存在
mkdir -p "$RUN_DIR"

# 主函数
main() {
	# 查找所有配置文件
	local configs=$(find "$CONFIG_DIR" -type f -name "*.conf" 2>/dev/null)
	if [ -z "$configs" ]; then
		echo "错误:在 $CONFIG_DIR 目录下未找到配置文件"
		exit 1
	fi

	# 处理所有实例
	case "$1" in
		start)
			for conf in $configs; do
				local port=$(echo "$conf" | sed 's/[^0-9]//g')
				local pidfile="$RUN_DIR/redis_${port}.pid"

				if [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null; then
					echo "Redis端口 $port 已运行"
					continue
				fi

				echo "启动Redis端口 $port..."
				$EXEC "$conf" &
				sleep 1

				if [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null; then
					echo "Redis端口 $port 启动成功"
				else
					echo "Redis端口 $port 启动失败"
				fi
			done
			;;
		stop)
			for conf in $configs; do
				local port=$(echo "$conf" | sed 's/[^0-9]//g')
				local pidfile="$RUN_DIR/redis_${port}.pid"

				if [ ! -f "$pidfile" ] || ! kill -0 $(cat "$pidfile") 2>/dev/null; then
					echo "Redis端口 $port 未运行"
					continue
				fi

				echo "停止Redis端口 $port..."
				$CLIEXEC -p $port shutdown

				# 等待服务停止 (30秒超时)
				local timeout=30
				while [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null && [ $timeout -gt 0 ]; do
					sleep 1
					timeout=$((timeout-1))
				done

				# 强制终止(如果需要)
				if [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null; then
					echo "警告: 强制终止Redis端口 $port"
					kill -9 $(cat "$pidfile")
				fi

				echo "Redis端口 $port 已停止"
			done
			;;
		restart)
			echo "重启所有Redis实例..."
			$0 stop
			sleep 1
			$0 start
			;;
		status)
			echo "检查Redis实例状态..."
			for conf in $configs; do
				local port=$(echo "$conf" | sed 's/[^0-9]//g')
				local pidfile="$RUN_DIR/redis_${port}.pid"

				if [ -f "$pidfile" ] && kill -0 $(cat "$pidfile") 2>/dev/null; then
					local pid=$(cat "$pidfile")
					echo "Redis端口 $port (PID: $pid) 运行中"

					# 尝试获取Redis信息
					local info=$($CLIEXEC -p $port info 2>/dev/null)
					if [ -n "$info" ]; then
						local version=$(echo "$info" | grep "redis_version" | cut -d: -f2)
						local memory=$(echo "$info" | grep "used_memory_human" | cut -d: -f2)
						echo "  版本: $version"
						echo "  内存: $memory"
					fi
				else
					echo "Redis端口 $port 未运行"
				fi
			done
			;;
		*)
			echo "用法: $0 {start|stop|restart|status}"
			exit 1
			;;
	esac
}

# 执行主函数
main "$@"
EOF

    # 赋予脚本执行权限
    chmod +x /etc/init.d/redis
    chown root:root /etc/init.d/redis
    
    log "INFO" "Redis管理脚本生成完成"
    return 0
}

# ==================== 集群管理 ====================

setup_cluster() {
    log "INFO" "开始配置Redis集群..."
    
    # 创建数据目录
    mkdir -p "$REDIS_DBS_PATH" || return 1
    
    # 启动所有实例
    log "INFO" "启动Redis实例..."
    find "$REDIS_CATALOG_PATH" -name "*.conf" | xargs -L 1 "$REDIS_EXECUTABLE" || {
        log "ERROR" "启动Redis实例失败"
        return 1
    }
    sleep 5

    log "INFO" "等待Redis实例启动..."
    for port in "${CLUSTER_PORTS[@]}"; do
        local max_retries=10
        local retry=0
        while [ $retry -lt $max_retries ]; do
            if redis-cli -h "$IP" -p "$port" ping >/dev/null 2>&1; then
                log "INFO" "端口 $port 启动成功"
                break
            fi
            retry=$((retry+1))
            sleep 1
        done
        
        if [ $retry -eq $max_retries ]; then
            log "ERROR" "端口 $port 启动失败"
            return 1
        fi
    done

    # 让节点相互认识
    for ((j = 0; j < CLUSTER_GROUPS; j++)); do
        now_pos=$((j * CLUSTER_NODES_PER_GROUP))
        main_port=${CLUSTER_PORTS[now_pos]}

        for ((i = now_pos + 1; i < now_pos + CLUSTER_NODES_PER_GROUP; i++)); do
            port=${CLUSTER_PORTS[i]}
            if ! redis-cli -h "$IP" -p "$main_port" cluster meet "$IP" "$port"; then
                log "ERROR" "无法让节点 $port 加入集群"
                return 1
            fi
            sleep 1
        done
    done

    # 配置主从关系
    for ((j = 0; j < CLUSTER_GROUPS; j++)); do
        now_pos=$((j * CLUSTER_NODES_PER_GROUP))
        
        for ((p = 0; p < CLUSTER_MASTERS; p++)); do
            master_pos=$((now_pos + p))
            slave_pos=$((master_pos + CLUSTER_MASTERS))
            master_port=${CLUSTER_PORTS[master_pos]}
            slave_port=${CLUSTER_PORTS[slave_pos]}
            
            master_id=$(redis-cli -h "$IP" -p "$slave_port" cluster nodes | grep "$master_port" | awk '{print $1}')
            if [ -z "$master_id" ]; then
                log "ERROR" "无法获取主节点ID: $master_port"
                return 1
            fi
            
            if ! redis-cli -h "$IP" -p "$slave_port" cluster replicate "$master_id"; then
                log "ERROR" "配置主从复制失败: $master_port -> $slave_port"
                return 1
            fi
            sleep 1
        done
    done

    sleep 2  # 等待主从关系稳定

    log "INFO" "开始分配集群槽点..."
    local total_slots=16384
    local slots_per_master=$((total_slots / CLUSTER_MASTERS))
    local remaining_slots=$((total_slots % CLUSTER_MASTERS))
    log "INFO" "总槽点数: $total_slots, 主节点数: $CLUSTER_MASTERS, 每主节点分配: $slots_per_master 个槽点"
    
    # 存储所有集群的主节点信息
    declare -a all_primary_masters  # 存储每个集群的第一个主节点(用于最终验证)
    
    # 收集所有主节点端口并分配槽点
    for ((j = 0; j < CLUSTER_GROUPS; j++)); do
        local master_ports=()

        local now_pos=$((j * CLUSTER_NODES_PER_GROUP))
        for ((p = 0; p < CLUSTER_MASTERS; p++)); do
            local master_pos=$((now_pos + p))
            master_ports+=("${CLUSTER_PORTS[master_pos]}")
        done
    
        # 分配槽点
        local start_slot=0
        for ((i = 0; i < CLUSTER_MASTERS; i++)); do
            local end_slot=$((start_slot + slots_per_master - 1))
            local port=${master_ports[i]}
            
            # 处理最后一个主节点分配剩余槽点
            if [ $i -eq $((CLUSTER_MASTERS - 1)) ]; then
                end_slot=$((start_slot + slots_per_master + remaining_slots - 1))
            fi
            
            log "INFO" "集群 $((j+1)): 分配槽点 $start_slot-$end_slot 到主节点 $port"
            
            # 使用 redis-cli 命令分配槽点
            if ! redis-cli -h "$IP" -p "$port" cluster addslots $(seq $start_slot $end_slot); then
                log "ERROR" "集群 $((j+1)): 槽点分配失败: $start_slot-$end_slot 到 $port"
                return 1
            fi
            
            start_slot=$((end_slot + 1))
        done
        
        # 记录每个集群的第一个主节点(用于最终验证)
        all_primary_masters[$j]=${master_ports[0]}
        
        log "INFO" "集群 $((j+1)): 槽点分配命令已执行,等待统一验证..."
    done
    
    # 等待所有集群稳定
    log "INFO" "所有集群槽点分配命令已执行,等待集群稳定..."
    sleep 10  # 根据集群规模调整等待时间
    
    # 统一验证所有集群
    log "INFO" "开始统一验证所有集群的槽点分配..."
    for ((j = 0; j < CLUSTER_GROUPS; j++)); do
        local primary_master=${all_primary_masters[$j]}
        local slots_assigned=0
        local timeout=30
        local start_time=$(date +%s)
        
        log "INFO" "集群 $((j+1)): 验证槽点分配状态..."
        
        # 增加等待循环,确保集群有足够时间同步
        while true; do
            slots_assigned=$(redis-cli -h "$IP" -p "$primary_master" cluster info | grep "cluster_slots_assigned" | awk -F: '{print $2}' | tr -d '[:space:]')
            
            # 检查是否获取到有效数据
            if [ -z "$slots_assigned" ] || ! [[ "$slots_assigned" =~ ^[0-9]+$ ]]; then
                log "INFO" "集群 $((j+1)): 暂时无法获取槽点信息,继续等待..."
            elif [ "$slots_assigned" -eq 16384 ]; then
                log "INFO" "集群 $((j+1)): 槽点分配验证通过!"
                break
            else
                log "INFO" "集群 $((j+1)): 槽点分配中: $slots_assigned/16384,继续等待..."
            fi
            
            # 检查是否超时
            local elapsed=$(( $(date +%s) - start_time ))
            if [ "$elapsed" -ge "$timeout" ]; then
                log "ERROR" "集群 $((j+1)): 槽点分配验证超时!"
                break
            fi
            
            sleep 2
        done
        
        # 最终验证
        if [ "$slots_assigned" -ne 16384 ]; then
            log "ERROR" "集群 $((j+1)): 槽点分配不完整: $slots_assigned/16384"
            
            # 获取该集群的所有主节点信息
            local master_ports=()
            local now_pos=$((j * CLUSTER_NODES_PER_GROUP))
            for ((p = 0; p < CLUSTER_MASTERS; p++)); do
                local master_pos=$((now_pos + p))
                master_ports+=("${CLUSTER_PORTS[master_pos]}")
            done
            
            # 输出详细诊断信息
            log "ERROR" "集群 $((j+1)): 所有节点槽点分配详情:"
            for port in "${master_ports[@]}"; do
                log "ERROR" "集群 $((j+1)): 节点 $port 信息:"
                redis-cli -h "$IP" -p "$port" cluster nodes | while read line; do
                    log "ERROR" "$line"
                done
            done
            
            return 1
        fi
    done
    
    # 输出所有集群的最终状态
    log "INFO" "所有集群槽点分配验证通过!"
    log "INFO" "集群状态汇总:"
    
    for ((j = 0; j < CLUSTER_GROUPS; j++)); do
        local primary_master=${all_primary_masters[$j]}
        log "INFO" "集群 $((j+1)) 状态:"
        redis-cli -h "$IP" -p "$primary_master" cluster info | while read line; do
            log "INFO" "$line"
        done
        log "INFO" "------------------------"
    done
    
    log "INFO" "Redis集群配置完成"
    return 0
}

# ==================== 主流程 ====================

main() {
    init_logging
    log "INFO" "开始部署Redis集群"
    log "INFO" "主机IP: $IP"

    # 1. 定义步骤执行顺序(有序数组)
    local step_names=(
        "验证配置"
        "清理环境"
		"检查Redis安装"
        "安装Redis"
        "配置集群节点"
        "配置单机节点"
        "设置集群"
        "生成管理脚本"
    )

    # 2. 定义步骤名称到函数的映射(关联数组)
    declare -A steps=(
		["验证配置"]="validate_config"
		["清理环境"]="clean_environment"
		["检查Redis安装"]="check_redis"
		["安装Redis"]="install_redis"
		["配置集群节点"]="configure_cluster_nodes"
		["配置单机节点"]="configure_standalone_nodes"
		["设置集群"]="setup_cluster"
		["生成管理脚本"]="generate_management_script"
	)

    local total_steps=${#step_names[@]}
    local current_step=0

    # 执行部署步骤
    for step in "${step_names[@]}"; do
        ((current_step++))
        show_progress "$current_step" "$total_steps" "$step"
        
        case "$step" in
            "检查Redis安装")
                if ! check_redis; then
                    install_redis || exit 1
                fi
                ;;
            "配置集群节点")
                for port in "${CLUSTER_PORTS[@]}"; do
                    configure_cluster_nodes "$port" || exit 1
                done
                ;;
            "配置单机节点")
                local index=0
                
                for port in "${STANDALONE_PORTS[@]}"; do
                    local master_port=""
                    if (( index % 2 == 1 )); then  
                        master_port=${STANDALONE_PORTS[$index-1]}  # 前一个端口(偶数索引)为主节点
                    else
                        master_port=""  # 主节点不需要设置master_port(函数中忽略)
                    fi
                    
                    configure_standalone_nodes "$port" "$master_port" "$index" || exit 1
                    ((index++))
                done
                ;;
            *)
                ${steps[$step]} || exit 1
                ;;
        esac
    done

    log "INFO" "部署成功完成"
    echo -e "\n部署结果:"
    echo "集群端口: ${CLUSTER_PORTS[*]}"
    echo "单机端口: ${STANDALONE_PORTS[*]}"
    echo "管理命令: service redis {start|stop|restart|status}"
}

# 执行主函数
main "$@"
相关推荐
林太白几秒前
Next.js超简洁完整篇
前端·后端·react.js
前端付豪1 分钟前
汇丰登录风控体系拆解:一次 FaceID 被模拟攻击的调查纪实
前端·后端·架构
无名之逆12 分钟前
大三自学笔记:探索Hyperlane框架的心路历程
java·开发语言·前端·spring boot·后端·rust·编程
yang_xiao_wu_12 分钟前
springboot+mybatis面试题
spring boot·后端·mybatis
Chuck1sn14 分钟前
我把 Cursor AI 整合到 Ruoyi 中,从此让 Java 脚手架脱离人工!
java·vue.js·后端
水木石画室17 分钟前
Spring Boot 常用注解面试题深度解析
java·spring boot·后端
阿杆19 分钟前
想体验出海应用赚钱?试试这个一年免费的香港服务器
后端·产品·创业
汤圆和烟灰儿38 分钟前
Linux 防火墙工具 iptables 入门与常用命令整理
后端
bcbnb38 分钟前
iOS性能调试完整流程实录:工具组合下的问题定位与修复实践(含keymob)
后端
上车函予38 分钟前
干掉图形验证码!基于PoW的Cap验证码集成指南
前端·后端