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 "$@"
相关推荐
IT_102424 分钟前
Spring Boot项目开发实战销售管理系统——数据库设计!
java·开发语言·数据库·spring boot·后端·oracle
bobz96538 分钟前
动态规划
后端
stark张宇1 小时前
VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
linux·后端
亚力山大抵2 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍2 小时前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端
CHENWENFEIc3 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试
重庆小透明3 小时前
力扣刷题记录【1】146.LRU缓存
java·后端·学习·算法·leetcode·缓存
博观而约取4 小时前
Django 数据迁移全解析:makemigrations & migrate 常见错误与解决方案
后端·python·django
寻月隐君4 小时前
Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式
后端·rust·github
GO兔4 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go