Redis 安装部署[主从、哨兵、集群](linux版)

说明:如果打算将本文内容应用于生产环境,建议对相关参数进行适当调整,以确保系统的稳定性和性能优化。

背景

长期以来,我们一直在使用Redis,但始终未能形成一个高效的运维模式来快速搭建Redis环境。因此,我编写了这份文档,旨在作为一份备忘录,同时也希望能与团队分享,以便我们能够更高效地协作和提升工作效率。

为了便于大家理解,接下来我将展示如何通过编写Shell脚本快速搭建Redis环境。如果条件允许,也可以基于这些脚本逻辑,采用Docker容器化技术来构建Redis,以实现更加灵活和便捷的部署。

一、Redis 下载安装

bash 复制代码
# 进入安装目录
cd /home/middleware/redis

# 下载
wget https://download.redis.io/releases/redis-7.4.0.tar.gz

# 解压
tar -zxvf redis-7.4.0.tar.gz

# 进入redis目录
cd redis-7.4.0

# 下载相关依赖
yum -y install gcc gcc-c++ tcl automake autoconf libtool make

# 安装编译redis(安装后,redis默认设置到环境变量中)
make && make install

二、Redis 主从复制 搭建

  • 主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。
  • 主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。
  • 缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。

1、创建配置目录

bash 复制代码
# 创建配置目录(下面的脚本都放到该目录,具体目录可自定义)
mkdir /home/middleware/redis/redis-master-slave

cd /home/middleware/redis/redis-master-slave

2、创建模板配置文件 redis.conf.template

  • 创建配置文件
bash 复制代码
# 创建配置文件
vim redis.conf.template
  • 模板配置文件内容如下:
  • 模板配置文件中的占位符:
    • custom_port 自定义端口占位符
    • master_pwd 主节点密码
    • replicaof_master_ip_port 从节点同步主节点占位符
bash 复制代码
# 修改监听地址
bind 0.0.0.0

# 关闭保护模式,默认yes,让redis支持远程连接
protected-mode no

# redis监听端口,默认6379
port custom_port

# 开启守护进程,以独立进程启动,默认no
daemonize yes

# 设置pidfile,默认redis.pid
pidfile redis_custom_port.pid

# 日志文件,默认redis.log
logfile "redis_custom_port.log"

# 禁用save命令,同步执行有性能问题,推荐使用bgsave
save ""

# 设置快照文件名称,默认dump.rdb
dbfilename dump_custom_port.rdb

# 设置数据目录,默认./
dir ./

# 连接主节点的密码,配置在从节点,默认12345
masterauth 'master_pwd'

# 连接Redis服务的redis密码,配置在主节点,默认''
requirepass 'master_pwd' 

# 设置最大内存,单位kb/mb/gb
maxmemory 256mb

# 设置内存淘汰策略,默认noeviction不淘汰数据
# volatile-lru 按照LRU算法逐出原有数据,但仅逐出设置了过期时间的数据
maxmemory-policy volatile-lru

# 开启AOF持久化,默认no
appendonly yes

# AOF持久化文件名称,默认appendonly.aof
appendfilename "appendonly_custom_port.aof"


# 从节点配置 -----------------------------
# 指定要同步的Master节点IP和端口
# replicaof 127.0.0.1 6379
#replicaof_master_ip_port

3、创建控制脚本 redis-master-slave.sh

  • 创建脚本
bash 复制代码
# 创建脚本
vim redis-master-slave.sh

# 授权
chmod 777 redis-master-slave.sh
  • 脚本内容如下:
  • 请根据自己需要,修改脚本中的属性
bash 复制代码
#!bin/bash

PROG_NAME=$0
ACTION=$1

# master ip和port
master_ip=172.18.31.17
master_port=6379
master_pwd=123456

# 一台机器上启动的从节点的端口列表
slave_ports=(6380 6381)

# 当前路径
curr_path=$(pwd)

# redis模板配置文件
redis_conf_tempalte=redis.conf.template

# redis模板配置文件中的占位符
old_char=custom_port
old_master_pwd=master_pwd


# 针对从节点配置,做特殊处理
old_replicaof="#replicaof_master_ip_port"
new_replicaof="replicaof $master_ip $master_port"


# 数组长度
slave_ports_len=${#slave_ports[@]}


usage() {
    cat << EOF
    该脚本用于操作 Redis 主从节点.
    Usage: $PROG_NAME Options
    Options:
    create_master_conf     批量生成主节点配置文件
    create_slave_conf      批量生成从节点配置文件
    start_master_node      批量启动主节点
    start_slave_node       批量启动从节点
    start_node             批量启动主节点和从节点
    stop_node              批量停止主节点和从节点
    restart_node           批量重启主节点和从节点
EOF
    exit 2
}


# --------------------------------------------------------------
# 第一步:生成redis配置文件(基于模板配置文件redis.conf.template)
# 作用:无需手动修改redis配置文件中的内容,可简化操作
# --------------------------------------------------------------

# 1、生成主节点redis配置文件
create_master_conf() {
    dest_filename=$master_port/redis-$master_port.conf

    if [ ! -d "$master_port" ]; then
        mkdir -p "$master_port"
        echo "Created directory: $master_port"
    fi

    if [ ! -e "$dest_filename" ]; then
        echo "复制配置文件: $dest_filename"
        cp $redis_conf_tempalte $dest_filename
        
        echo "替换配置文件中的占位符 $dest_filename"
        sed -i "s/$old_char/$master_port/g" $dest_filename
        sed -i "s/$old_master_pwd/$master_pwd/g" $dest_filename
        echo ""
    else
        echo "配置文件已存在: $dest_filename"
    fi
}

# 2、生成从节点redis配置文件
# 注意:每个从节点所在机器都需要执行一次
create_slave_conf() {
    for (( i=0; i<$slave_ports_len; i++ ))
    do
        new_char=${slave_ports[$i]}
        dest_filename=$new_char/redis-$new_char.conf
    
        if [ ! -d "$new_char" ]; then
            mkdir -p "$new_char"
            echo "Created directory: $new_char"
        fi
    
        if [ ! -e "$dest_filename" ]; then
            echo "复制配置文件: $dest_filename"
            cp $redis_conf_tempalte $dest_filename
            
            echo "替换配置文件中的占位符 $dest_filename"
            sed -i "s/$old_char/$new_char/g" $dest_filename
            sed -i "s/$old_master_pwd/$master_pwd/g" $dest_filename
            sed -i "s/$old_replicaof/$new_replicaof/g" $dest_filename
            echo ""
        else
            echo "配置文件已存在: $dest_filename"
        fi
    done
}


# --------------------------------------------------------------
# 第二步:启动redis节点
# --------------------------------------------------------------

# 1、启动redis主节点
start_master_node() {    
    echo "redis-$master_port start"
    
    cd $curr_path/$master_port
    # 异步执行
    nohup redis-server redis-$master_port.conf >/dev/null 2>&1 &

    echo "redis-$master_port end"
    echo ""
}

# 2、批量启动redis从节点
# 注意:每个从节点所在机器都需要执行一次
start_slave_node() {
    for (( i=0; i<$slave_ports_len; i++ ))
    do
        new_char=${slave_ports[$i]}
        echo "redis-$new_char start"
    
        cd $curr_path/$new_char
        # 异步执行
        nohup redis-server redis-$new_char.conf >/dev/null 2>&1 &
        echo "redis-$new_char end"
        echo ""
    done
}

# --------------------------------------------------------------
# 第三步:停止redis节点
# --------------------------------------------------------------
stop_node() {
    # 获取redis进程id列表,将ps awk 处理的信息转换为数组
    pids=(`ps -ef | grep 'redis-server' | grep -v grep |grep -v 'redis-master-slave.sh'| awk '{print$2}'`)

    if [ ${#pids[@]} -eq 0 ]; then
        echo -e "\rno redis process"
        return
    fi
    
    echo "stop redis process"
    times=60
    for e in $(seq 60)
    do
         sleep 1
         cost_time=$(($times - $e ))

         pids=(`ps -ef | grep 'redis-server' | grep -v grep |grep -v 'redis-master-slave.sh'| awk '{print$2}'`)
         if [ ${#pids[@]} -eq 0 ]; then
            echo -e "\rredis process has exited"
            break;
         fi
         
         for pid in ${pids[*]}
         do
             kill -9 $pid
             echo -e "\r -- $pid stopping redis lasts `expr $cost_time` seconds."
         done
    done
    echo ""
}


case "$ACTION" in
    create_master_conf)
        create_master_conf
    ;;
    create_slave_conf)
        create_slave_conf
    ;;
    start_master_node)
        start_master_node
    ;;
    start_slave_node)
        start_slave_node
    ;;
    start_node)
        start_master_node
        start_slave_node
    ;;
    stop_node)
        stop_node
    ;;
    restart_node)
        stop_node
        start_master_node
        start_slave_node
    ;;
    *)
        usage
    ;;
esac

4、执行控制脚本

  • 按顺序执行如下命令,搭建Redis 主从
bash 复制代码
# 主节点所在机器(172.18.31.17)

# 1、生成主节点配置文件
sh redis-master-slave.sh create_master_conf

# 2、生成从节点redis配置文件
sh redis-master-slave.sh create_slave_conf

# 3、启动主从节点
sh redis-master-slave.sh start_node

# 或者,分开启动主从节点
sh redis-master-slave.sh start_master_node
sh redis-master-slave.sh start_slave_node



# 从节点所在机器(172.18.31.16)
# 1、生成从节点redis配置文件
sh redis-master-slave.sh create_slave_conf

# 2、启动从节点
sh redis-master-slave.sh start_slave_node

5、测试命令

bash 复制代码
# 查看主从复制信息
redis-cli -h 127.0.0.1 -p 6379 -a 123456 info replication

# 验证主从复制

# 登录主节点
redis-cli -h 127.0.0.1 -p 6379 -a 123456

keys *

set name 123

get name


# 登录从节点
redis-cli -h 127.0.0.1 -p 6380 -a 123456

keys *

三、Redis Sentinel 搭建

注意: 新版本redis自带哨兵,不需要单独安装

  • 在上面主从复制模式的基础上,新增sentinel哨兵的能力。
  • Redis Sentinel是Redis官方提供的高可用性解决方案,主要用于监控和管理Redis服务器,确保在主服务器发生故障时能够自动进行故障转移,从而保证服务的连续性和高可用性。
  • 主要功能
    • 主从监控:定期向主服务器和从服务器发送PING命令,检查它们是否在线
    • 故障转移:如果master 宕机,自动根据投票数将 slave 切换为新 master
    • 消息通知:哨兵可以将故障转移的结果发送给客户端
    • 配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址
  • 缺陷:写操作无法负载均衡;存储能力受到单机的限制;哨兵无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要对从节点做额外的监控、切换操作。

1、创建配置目录

bash 复制代码
# 创建配置目录(下面的脚本都放到该目录,具体目录可自定义)
mkdir /home/middleware/redis/redis-sentinel

cd /home/middleware/redis/redis-sentinel

2、创建模板配置文件 sentinel.conf.template

  • 创建配置文件
bash 复制代码
# 创建配置文件
vim sentinel.conf.template
  • 模板配置文件内容如下:
  • 模板配置文件中的占位符:custom_port 自定义端口占位符
bash 复制代码
# 关闭保护模式,让redis支持远程连接
protected-mode no

# 端口,默认26379
port custom_port

# 指定sentinel为后台启动
daemonize yes

# 设置pidfile,默认redis-sentinel.pid
pidfile sentinel_custom_port.pid

# 日志存放路径
logfile "sentinel_custom_port.log"

# 指定数据存放路径
dir ./

# 配置监听主服务器
# mymaster:自定义redis主节点名称,在一个sentinel网络中,一个redis主节点只能有一个名称
# 172.19.223.161:表示主节点ip
# 6379:表示主节点port
# 2:表示至少需要2个哨兵认为主节点不可⽤时,才会进⾏failover操作
sentinel monitor mymaster master_ip master_port 2

# 配置服务密码
# mymaster:redis主节点名称(与上一致);
# 123456:redis主节点和从节点的密码
sentinel auth-pass mymaster master_pwd

# 判定服务器down掉的时间周期,默认30000毫秒(30秒)
sentinel down-after-milliseconds mymaster 30000

# 故障转移的最大超时时间为180000(180秒)
sentinel failover-timeout mymaster 180000

3、创建控制脚本 redis-sentinel.sh

  • 创建脚本
bash 复制代码
# 创建脚本
vim redis-sentinel.sh

# 授权
chmod 777 redis-sentinel.sh
  • 脚本内容如下:
  • 请根据自己需要,修改脚本中的属性
bash 复制代码
#!bin/bash

PROG_NAME=$0
ACTION=$1

# master ip和port
master_ip=172.18.31.17
master_port=6379
master_pwd=123456

# 一台机器上启动的sentinel的端口列表
sentinel_ports=(26379 26380 26381)

# 当前路径
curr_path=$(pwd)

# sentinel模板配置文件
sentinel_conf_tempalte=sentinel.conf.template

# 模板配置文件中的占位符
old_char=custom_port

# 主节点占位符
old_master_ip="master_ip"
old_master_port="master_port"
old_master_pwd="master_pwd"


# 数组长度
sentinel_ports_len=${#sentinel_ports[@]}


usage() {
    cat << EOF
    该脚本用于操作 Redis 主从节点.
    Usage: $PROG_NAME Options
    Options:
    create_sentinel_conf   批量生成sentinel配置文件
    start_sentinel_node    批量启动sentinel节点
    stop_node              批量停止sentinel节点
    restart_node           批量重启sentinel节点
EOF
    exit 2
}


# --------------------------------------------------------------
# 第一步:生成sentinel配置文件
# 注意:每个sentinel节点所在机器都需要执行一次
# --------------------------------------------------------------
create_sentinel_conf() {
    for (( i=0; i<$sentinel_ports_len; i++ ))
    do
        new_char=${sentinel_ports[$i]}
        dest_filename=$new_char/sentinel-$new_char.conf
    
        if [ ! -d "$new_char" ]; then
            mkdir -p "$new_char"
            echo "Created directory: $new_char"
        fi
    
        if [ ! -e "$dest_filename" ]; then
            echo "复制配置文件: $dest_filename"
            cp $sentinel_conf_tempalte $dest_filename
            
            echo "替换配置文件中的占位符 $dest_filename"
            sed -i "s/$old_char/$new_char/g" $dest_filename
            sed -i "s/$old_master_ip/$master_ip/g" $dest_filename
            sed -i "s/$old_master_port/$master_port/g" $dest_filename
            sed -i "s/$old_master_pwd/$master_pwd/g" $dest_filename
            echo ""
        else
            echo "配置文件已存在: $dest_filename"
        fi
    done
}


# --------------------------------------------------------------
# 第二步:启动sentinel节点
# 注意:每个sentinel节点所在机器都需要执行一次
# --------------------------------------------------------------
start_sentinel_node() {
    for (( i=0; i<$sentinel_ports_len; i++ ))
    do
        new_char=${sentinel_ports[$i]}
        echo "sentinel-$new_char start"
    
        cd $curr_path/$new_char
        nohup redis-sentinel sentinel-$new_char.conf >/dev/null 2>&1 &
        
        echo "sentinel-$new_char end"
        echo ""
    done
}

# --------------------------------------------------------------
# 第三步:停止sentinel节点
# --------------------------------------------------------------
stop_node() {
    # 获取redis进程id列表,将ps awk 处理的信息转换为数组
    pids=(`ps -ef | grep 'redis-sentinel' | grep -v grep |grep -v 'redis-sentinel.sh'| awk '{print$2}'`)

    if [ ${#pids[@]} -eq 0 ]; then
        echo -e "\rno redis process"
        return
    fi
    
    echo "stop redis process"
    times=60
    for e in $(seq 60)
    do
         sleep 1
         cost_time=$(($times - $e ))

         pids=(`ps -ef | grep 'redis-sentinel' | grep -v grep |grep -v 'redis-sentinel.sh'| awk '{print$2}'`)
         if [ ${#pids[@]} -eq 0 ]; then
            echo -e "\rredis process has exited"
            break;
         fi
         
         for pid in ${pids[*]}
         do
             kill -9 $pid
             echo -e "\r -- $pid stopping redis lasts `expr $cost_time` seconds."
         done
    done
    echo ""
}


case "$ACTION" in
    create_sentinel_conf)
        create_sentinel_conf
    ;;
    start_sentinel_node)
        start_sentinel_node
    ;;
    stop_node)
        stop_node
    ;;
    restart_node)
        stop_node
        start_sentinel_node
    ;;
    *)
        usage
    ;;
esac

4、执行控制脚本

  • 按顺序执行如下命令,搭建Redis Sentinel
bash 复制代码
# 1、生成sentinel配置文件
sh redis-sentinel.sh create_sentinel_conf

# 2、启动sentinel节点
sh redis-sentinel.sh start_sentinel_node

5、测试命令

bash 复制代码
# 第一步:查看:sentinel 信息(主节点为6379)
[root@centos7 redis-sentinel]# redis-cli -h 127.0.0.1 -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.18.31.17:6379,slaves=2,sentinels=3

# 第二步:模拟:kill 掉主节点 6379
[root@centos7 redis-master-slave]# ps -ef | grep redis
root       2518      1  0 11:23 ?        00:00:07 redis-server 0.0.0.0:6380
root       2519      1  0 11:23 ?        00:00:07 redis-server 0.0.0.0:6379
root       2520      1  0 11:23 ?        00:00:07 redis-server 0.0.0.0:6381
root       2656      1  1 11:37 ?        00:00:03 redis-sentinel *:26379 [sentinel]
root       2657      1  1 11:37 ?        00:00:03 redis-sentinel *:26380 [sentinel]
root       2658      1  1 11:37 ?        00:00:03 redis-sentinel *:26381 [sentinel]
root       2696   1952  0 11:42 pts/0    00:00:00 grep --color=auto redis

[root@centos7 redis-master-slave]# kill -9 2519


# 第三步:等待几秒,查看:sentinel 信息(主节点为6381,说明已自动切换主节点)
[root@centos7 redis-sentinel]# redis-cli -h 127.0.0.1 -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=172.18.31.17:6381,slaves=2,sentinels=3


# 第四步:查看主从复制信息(6381变为主节点,且只有一个6380的从节点)
[root@centos7 redis-sentinel]# redis-cli -h 127.0.0.1 -p 6381 -a 123456 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.18.31.17,port=6380,state=online,offset=16852,lag=0
master_failover_state:no-failover
master_replid:b763222247189ca9cd4f51b69f3cca41e1b42c7e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:16852
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:16852

四、Redis Cluster 搭建【推荐】

  • Redis Cluster模式解决了写操作无法负载均衡,以及单机存储限制的问题,实现了较为完善的高可用方案。
  • Redis Cluster模式中集群节点最小配置6个节点(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

1、创建配置目录

bash 复制代码
# 创建集群配置目录(下面的脚本都放到该目录,具体目录可自定义)
mkdir -p /home/middleware/redis/redis-cluster

cd /home/middleware/redis/redis-cluster

2、创建模板配置文件 redis.cluster.conf.template

  • 创建配置文件
bash 复制代码
# 创建配置文件
vim redis.cluster.conf.template
  • 模板配置文件内容如下:
  • 模板配置文件中的占位符:custom_port
  • 说明:
    • 若有参数需调整,可先修改该模板配置文件的内容,再执行后面的脚本。
    • 重点注意 requirepass 和 maxmemory的配置。
bash 复制代码
# 注释掉 bind 项,默认监听所有网卡
# bind 127.0.0.1
bind 0.0.0.0

# 关闭保护模式,默认yes,让redis支持远程连接
protected-mode no

# redis监听端口,默认6379
port custom_port

# 开启守护进程,以独立进程启动,默认no
daemonize yes

# 设置pidfile,默认redis.pid
pidfile redis_custom_port.pid

# 日志文件,默认redis.log
logfile "redis_custom_port.log"

# 禁用save命令,同步执行有性能问题,推荐使用bgsave
save ""

# 默认yes【设置为no解决window11启动报错EXCEPTION_ACCESS_VIOLATION的问题】
stop-writes-on-bgsave-error no

# 设置快照文件名称,默认dump.rdb
dbfilename dump_custom_port.rdb

# 设置数据目录,默认./
dir ./

# 从节点重新规划周期,默认10
repl-ping-slave-period 10

# 连接主节点的密码,配置在从节点,默认12345
masterauth '123456'

# 连接Redis服务的redis密码,配置在主节点,默认''
# 集群版建议将各个节点的masterauth和requirepass设置为相同的密码,因为从节点也可能升级为主节点
requirepass '123456'

# 设置最大内存,单位kb/mb/gb
maxmemory 256mb

# 设置内存淘汰策略,默认noeviction不淘汰数据
# volatile-lru 按照LRU算法逐出原有数据,但仅逐出设置了过期时间的数据
maxmemory-policy allkeys-lru

# 开启AOF持久化,默认no
appendonly yes

# AOF持久化文件名称,默认appendonly.aof
appendfilename "appendonly_custom_port.aof"

# 开启群集功能,默认no
cluster-enabled yes

# 群集节点名称文件设置,默认nodes-6379.conf
cluster-config-file nodes_custom_port.conf

# 设置群集节点超时时间,默认15000
# 集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。
# 如果在cluster-node-timeout时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。默认15000,即15s。
cluster-node-timeout 15000

# 从节点有效因子,提高集群故障转移能力,默认10
# 每个从节点都要检查最后与主节点断线时间,判断其是否有资格替换故障的主节点。
# 如果从节点与主节点断线时间超过 (cluster-node-timeout * cluster-slave-validity-factor) + repl-ping-slave-period,则当前从节点不具备故障转移资格。
# 例如,如果节点超时时间为30秒,从节点有效因子为10,从节点重新规划周期为10秒,如果从节点与主节点断线时间超过310秒,则当前从节点不会尝试故障转移。
cluster-slave-validity-factor 10

# 迁移屏障,提高集群抵抗故障的能力,默认1
# 迁移屏障为1,表示只有当主节点至少保留一个从节点时,从节点才会迁移
# 要禁用迁移,只需将其设置为一个非常大的值。
cluster-migration-barrier 1

# 设置集群可用性,默认为yes
# yes 表示所有slot都正常工作,才能对外提供服务
# no  表示部分slot出现问题,其他正常的slot仍然可以继续提供服务
cluster-require-full-coverage yes

3、创建控制脚本 redis-cluster.sh

  • 创建脚本
bash 复制代码
# 创建脚本
vim redis-cluster.sh

# 授权
chmod 777 redis-cluster.sh
  • 脚本内容如下:
  • 请根据自己的需要,修改脚本中的属性:hosts、ports、redis_pwd
bash 复制代码
#!bin/bash

PROG_NAME=$0
ACTION=$1

# 单机集群配置
# 举例:3主3从,1个ip,6个port,即一台机器6个节点
# hosts=("127.0.0.1")
# ports=(6001 6002 6003 6004 6005 6006)

# 多机集群配置
# 举例:3主3从,3个ip,2个port,即一台机器2个节点,3主3
hosts=("172.18.31.15" "172.18.31.16" "172.18.31.17")
ports=(6001 6002)

# redis密码,对应模板配置文件redis.cluster.conf.template中的密码
redis_pwd=123456

# 数组长度
host_len=${#hosts[@]}
port_len=${#ports[@]}

# 当前路径
curr_path=$(pwd)

# redis模板配置文件
redis_conf_tempalte=redis.cluster.conf.template

# redis模板配置文件中的占位符
old_char=custom_port

usage() {
    cat << EOF
    该脚本用于操作 Redis Cluster.
    Usage: $PROG_NAME Options
    Options:
    create_conf     批量生成redis配置文件
    start_node      批量启动redis集群节点
    stop_node       批量停止redis集群节点
    restart_node    批量重启redis集群节点
    create_cluster  创建redis集群
EOF
    exit 2
}

# --------------------------------------------------------------
# 第一步:批量生成redis配置文件(基于模板配置文件redis.cluster.conf.template)
# 注意:每台机器都需要执行
# 作用:无需手动修改redis配置文件中的内容,可简化操作
# --------------------------------------------------------------
create_conf() {
    for (( i=0; i<$port_len; i++ ))
    do
        new_char=${ports[$i]}
        dest_filename=$new_char/redis-$new_char.conf
    
        if [ ! -d "$new_char" ]; then
            mkdir -p "$new_char"
            echo "Created directory: $new_char"
        fi
    
        if [ ! -e "$dest_filename" ]; then
            echo "复制配置文件: $dest_filename"
            cp $redis_conf_tempalte $dest_filename
            
            echo "替换配置文件中的占位符 $dest_filename"
            sed -i "s/$old_char/$new_char/g" $dest_filename
            echo ""
        else
            echo "配置文件已存在: $dest_filename"
        fi
    done
}


# --------------------------------------------------------------
# 第二步:批量启动redis集群节点
# 注意:每台机器都需要执行
# --------------------------------------------------------------
start_node() {
    for (( i=0; i<$port_len; i++ ))
    do
        new_char=${ports[$i]}
        echo "redis-$new_char start"
    
        cd $curr_path/$new_char
        # 异步执行
        nohup redis-server redis-$new_char.conf >/dev/null 2>&1 &
        echo "redis-$new_char end"
        echo ""
    done
}


# 批量停止redis集群节点
stop_node() {
    # 获取redis进程id列表,将ps awk 处理的信息转换为数组
    pids=(`ps -ef | grep 'redis-server' | grep -v grep |grep -v 'redis-cluster.sh'| awk '{print$2}'`)

    if [ ${#pids[@]} -eq 0 ]; then
        echo -e "\rno redis process"
        return
    fi
    
    echo "stop redis process"
    times=60
    for e in $(seq 60)
    do
         sleep 1
         cost_time=$(($times - $e ))

         pids=(`ps -ef | grep 'redis-server' | grep -v grep |grep -v 'redis-cluster.sh'| awk '{print$2}'`)
         if [ ${#pids[@]} -eq 0 ]; then
            echo -e "\rredis process has exited"
            break;
         fi
         
         for pid in ${pids[*]}
         do
             kill -9 $pid
             echo -e "\r -- $pid stopping redis lasts `expr $cost_time` seconds."
         done
    done
    echo ""
}

# --------------------------------------------------------------
# 第三步:创建redis集群
# 注意:在集群中任意一台机器上执行,且执行一次即可
# --------------------------------------------------------------
create_cluster() {
    # 生成 ip:port 格式串
    ip_port_list=""
    for (( i=0; i<$host_len; i++ ))
    do
        for (( j=0; j<$port_len; j++ ))
        do
            ip_port_list+=" ${hosts[$i]}:${ports[$j]}"
        done
    done
    echo "节点列表: $ip_port_list"
    
    # 执行创建集群命令
    # --cluster create 表示创建集群
    # --replicas 1 表示每个主节点有1个从节点,这里随机分配主从关系。如果需要定制,则可以不加该参数,使用add-node来定制。
    echo yes | redis-cli --cluster create $ip_port_list --cluster-replicas 1 -a $redis_pwd
}


case "$ACTION" in
    create_conf)
        create_conf
    ;;
    start_node)
        start_node
    ;;
    stop_node)
        stop_node
    ;;
    restart_node)
        stop_node
        start_node
    ;;
    create_cluster)
        create_cluster
    ;;
    *)
        usage
    ;;
esac

4、执行控制脚本

  • 按顺序执行如下命令,搭建Redis Cluster
bash 复制代码
# 第一步:基于redis模板配置文件,一键批量生成redis集群中的节点配置文件
# 注意:每台机器都需要执行
sh redis-cluster.sh create_conf


# 第二步:批量启动redis集群节点
# 注意:每台机器都需要执行
sh redis-cluster.sh start_node

# 注意:集群所有节点都启动后,再执行下面步骤


# 第三步:创建redis集群
# 注意:在集群中任意一台机器上执行即可
sh redis-cluster.sh create_cluster


# 批量停止redis集群节点
# 注意:每台机器都需要执行
sh redis-cluster.sh stop_node


# 批量重启redis集群节点
# 注意:每台机器都需要执行
sh redis-cluster.sh restart_node

5、测试命令

bash 复制代码
# 测试集群

# -c 表示-c(cluster),连接集群时使用,可防止moved和ask异常
# -a 表示-a(auth),redis密码,无需手动auth命令
redis-cli -h 127.0.0.1 -p 6001 -c -a 123456

# 查看节点的哈希槽编号范围
cluster slots

# 设置name键的值
set name abcd
-> Redirected to slot [5798] located at 127.0.0.1:6002【说明:重定向到了6002】

# 查看name键的槽编号(6002)
cluster keyslot name

# 查看节点6002上name建是否存在(存在)
redis-cli -h 127.0.0.1 -p 6002 -c -a 123456
keys *

# 查看节点6003上name建是否存在(不存在)
redis-cli -h 127.0.0.1 -p 6003 -c -a 123456
keys *

# 在节点6003上获取name键的值
get name
-> Redirected to slot [5798] located at 127.0.0.1:6002【说明:重定向到了6002】
"abcd"


# 手动关闭节点
redis-cli -h 127.0.0.1 -p 6001 -a 123456 shutdown

# 查看redis节点信息
redis-cli -h 127.0.0.1 -p 6001 -a 123456 info

# 每1秒打印一次info的过滤信息
# -r 表示将命令循环多少次
# -i 表示每隔几秒执行一次命令
redis-cli -h 127.0.0.1 -p 6001 -a 123456 -r 10 -i 1 info | grep used_memory_human

6、集群操作

查看集群命令帮助说明

bash 复制代码
# 
redis-cli --cluster help

查看集群节点id nodes

bash 复制代码
# 查看集群中的节点id
redis-cli -h 127.0.0.1 -p 6001  -a 123456 cluster nodes

# 获取slot分配
redis-cli -c -p 127.0.0.1 -p 6001  -a 123456 cluster slots

# 查看主从复制信息
redis-cli -h 127.0.0.1 -p 7003 -a 123456 -c info replication

查看集群 info

bash 复制代码
# 查看集群
redis-cli --cluster info 127.0.0.1:6001 -a 123456

检查集群 check

bash 复制代码
# 检查集群
redis-cli --cluster check 127.0.0.1:6001 -a 123456

# 检查集群中是否有槽同时被分配给了多个节点,只有当所有的槽位正常时,集群状态才算OK
redis-cli --cluster check 127.0.0.1:6001 --cluster-search-multiple-owners -a 123456

迁移槽位 reshard

bash 复制代码
# 命令解释
# 迁移一个或者多个节点上的槽位至一个目标节点上
# --cluster-from <arg>   #槽位来源节点id,多个用,分割,all表示全部节点,值不为all的情况下,不能包含--cluster-to
# --cluster-to <arg>     #槽位目标节点id,只允一个
# --cluster-slots <arg>  #迁移的槽位数
# --cluster-yes          #是否默认同意集群内部的迁移计划(默认同意就可以)
# --cluster-timeout <arg>   #迁移命令(migrate)的超时时间
# --cluster-pipeline <arg>  #迁移key时,一次取出的key数量,默认10
# --cluster-replace         #是否直接replace到目标节点


# 迁移槽位,迁移100个槽位,将100个槽位从6001,迁移到节点6002
redis-cli --cluster reshard 127.0.0.1:6001 --cluster-from 7e482eb54f131ef01f5050e600f12d1980ea7f1e --cluster-to 9db0701a7d10a3044763b7a17365933eeab47d08 --cluster-slots 100 --cluster-yes -a 123456

平衡集群 rebalance

bash 复制代码
# 命令解释
# rebalance host:port
# --cluster-weight <node1=w1...nodeN=wN> # 槽位权重(浮点型)比值,例如(这里使用端口号代替运行id): 7001=1 7002=1 7003=2 则表示,总权重为4, 7001和7002将分配到 (16384/4)*1  个槽位,7003则是(16384/4)*2个槽位。
# --cluster-use-empty-masters
# --cluster-timeout <arg>      # 迁移命令(migrate)的超时时间
# --cluster-simulate           # 模拟rebalance操作,不会真正执行迁移操作
# --cluster-pipeline <arg>     # 定义 getkeysinslot命令一次取出的key数量,默认值为10
# --cluster-threshold <arg>    # 平衡触发的阈值条件,默认为2.00%。例如上一步,7001和7002应该有4096个槽位,如果7001的槽位数不够4096个,且超过 (4096*0.02 约等于)82个及以上;或者7001的槽位数比4096少于82个及以上,则会触发自平衡。
# --cluster-replace            # 是否直接replace到目标节点


# 重新分配集群各个节点负责的槽位,让各个节点负责的槽数量重新回到平均状态
redis-cli --cluster rebalance 127.0.0.1:6001 -a 123456

# 平均分配所有的槽位:不手动指定槽位分配,自动将16384个槽位,分配给集群的每一个master
redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-use-empty-masters --cluster-threshold 1 -a 123456

# 按权重分配槽位:把node3上的槽位,平均分配到其他主节点上去
# 各个节点的权重都为1时,表示平均分配所有的槽位
redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-weight node1=1 node2=1 node3=0 -a 123456


# 按权重分配槽位:总权重为4,node1权重=1 node2权重=1 node3权重=2
redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-weight 7e482eb54f131ef01f5050e600f12d1980ea7f1e=1 9db0701a7d10a3044763b7a17365933eeab47d08=1 7879e1312ab7d13267357bceb58f99a9ce15cd9e=2 -a 123456

集群扩容 add-node

  • 扩容,即向集群中添加主节点或从节点
  • 扩容后,迁移槽位时,如何保证不影响 redis 的使用?(待研究)
  • 在不影响集群对外服务的情况下,为集群添加节点进行扩容,也可以下线部分节点进行缩容
  • 扩容步骤:
    • 第一步:添加节点
    • 第二步:迁移槽位 or 槽位平衡
bash 复制代码
# 命令解释
# add-node 
# new_host:new_port            #新加入集群的ip和port
# existing_host:existing_port  #集群中任一节点的ip和port
# --cluster-slave              #新节点作为从节点,默认随机一个主节点
# --cluster-master-id <arg>    #给新节点指定主节点,值为节点的运行id


# 添加主节点
# 不传入--cluster-slave --cluster-master-id 参数,表示添加一个主节点。
# 注意,当添加的是一个主节点时,此时,该主节点没有任何槽位,可以使用rebalance或者reshard来迁移槽位给它。
redis-cli --cluster add-node 127.0.0.1:6007 127.0.0.1:6001 -a 123456

# 槽位平衡:自动将16384个槽位,分配给集群的每一个master,不用手动指定槽位分配
redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-use-empty-masters --cluster-threshold 1 -a 123456

# 添加从节点(方式1)
# 假设现在集群中有6001 6002 6003 6007四个主节点,想为6007添加一个从节点127.0.0.1:6008,命令如下:
redis-cli --cluster add-node 127.0.0.1:6008 127.0.0.1:6001 --cluster-slave --cluster-master-id 0115ad0b7bbb3b95ff8e94388c14cf9189b8d2fe -a 123456

# 添加从节点(方式2),随机跟一个主节点
# 注意,如果没有指定主节点,那么会去一个副本数更少的主节点做副本;如果副本数都一样,那么就随机跟一个主节点;
redis-cli --cluster add-node 127.0.0.1:6008 127.0.0.1:6001 --cluster-slave -a 123456

说明:redis集群扩容,槽位迁移时,可能会遇到超时问题,解决方案具体见下文的常见错误-问题4

集群缩容 del-node

  • 缩容步骤:
    • 第一步:先迁移槽位 or 槽位平衡
    • 第二步:删除节点
bash 复制代码
# 命令解释
# del-node host:port node_id     #删除给定的一个节点,成功后关闭该节点服务
# 用于从集群中删除指定的节点。host:port是执行命令的节点地址和端口,node_id是要删除的节点ID。

# 删除从节点
redis-cli --cluster del-node 127.0.0.1:6008 98da58b16dc8becd4a1474e772ae4e24b44f0e1e -a 123456

# 按权重分配槽位:把node4上的槽位,平均分配到其他主节点上去
# 注意,如果把主节点的 slot 都迁移走了,那么该主节点下的从库会自动转移到其他有 slot 的主节点去
redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-weight c2fce950a3fb1c2243009f0ae34b9165f2c0242d=1 f413bd23d52255b292119d144fe0e44093044cd0=1 9470d0c3955a334efdcfa8ba79103f4b7593c8c9=1 c3f2a562b827d96e39bc285d9c33269c4e2e0d1f=0 -a 123456 

# 删除主节点
redis-cli --cluster del-node 127.0.0.1:6007 07f3c418b8796197621fe9e9ea839a2c0c1289b3 -a 123456

修复集群 fix

bash 复制代码
# 修复集群 

# --cluster-search-multiple-owners:是否修复多个拥有者的槽位。当集群中的槽位在迁移过程中,出现意外时,使用fix可修复该槽位。

redis-cli --cluster fix 127.0.0.1:6001 --cluster-search-multiple-owners -a 123456

redis-cli --cluster fix 127.0.0.1:5001 -a 123456

# --cluster-fix-with-unreachable-masters:是否修复不可达的主节点上的槽位。例如,集群中某个主节点坏掉了,也没有故障转移成功。此时如何恢复该主节点上的所有槽位呢?这时就可以使用该参数,会将处于该主节点上的所有槽位恢复到存活的主节点上(之前的数据会丢失,仅仅是恢复了槽位)。

redis-cli --cluster fix 127.0.0.1:6001 --cluster-fix-with-unreachable-masters -a 123456

集群上执行命令 call

bash 复制代码
# 命令解释
# call host:port command arg arg .. arg      #在集群的所有节点执行相关命令
# --cluster-only-masters                     #是否只在主节点上执行
# --cluster-only-replicas                    #是否只在从节点上执行

redis-cli --cluster call 127.0.0.1:6001 keys * -a 123456
redis-cli --cluster call 127.0.0.1:6001 set a aaa -a 123456
redis-cli --cluster call 127.0.0.1:6001 set b bbb -a 123456
redis-cli --cluster call 127.0.0.1:6001 set c ccc -a 123456
redis-cli --cluster call 127.0.0.1:6001 set d ddd -a 123456

集群节点超时时间设置 set-timeout

  • 不建议在线去设置,有遇到过在线设置,设置成功但导致主节点挂掉的情况
bash 复制代码
# 命令解释
# set-timeout host:port milliseconds     #设置整个集群的cluster-node-timeout时间

redis-cli --cluster set-timeout 127.0.0.1:6001 15000 -a 123456

导入数据至集群 import

  • 实际项目上,数据导入应该用 redis-shake 工具的比较多。
bash 复制代码
# 命令解释
# import         host:port
# --cluster-from host:port  # 来源redis node,不能为cluster node
# --cluster-from-user <arg> # 
# --cluster-from-pass <arg> # 来源redis node的密码
# --cluster-from-askpass
# --cluster-copy              #migrate时指定类型为copy
# --cluster-replace           #migrate时指定类型为replace


# 说明:外部Redis实例(127.0.0.2:6379)导入到集群中的任意一节点,导入之后,原来集群的key变为空,导入到新集群的key会自动分片到各个master节点的slot
# --cluster-replace 如果集群(127.0.01:6379)中存在外部redis实例(127.0.0.2:6379)的key,则会覆盖掉(10.35.2.68:6379)的value
# --cluster-copy 默认情况下,import 命令在向集群导入数据的同时,还会删除单机服务器中源数据。如果用户想要保留单机服务器中的数据,那么可以在执行命令的同时给定 --cluster-copy 选项 该命令将正在运行的实例的所有键(从源实例中删除键)移动到指定的预先存在的 Redis 集群。

redis-cli --cluster import 127.0.0.1:6001 --cluster-from 127.0.0.1:6379  --cluster-replace --cluster-copy -a 123456

备份集群rdb文件 backup

bash 复制代码
# 命令解释
# backup host:port backup_directory     #备份主节点上的数据RDB文件

mkdir backup
redis-cli --cluster backup 127.0.0.1:6001 ./backup -a 123456

五、常见错误

问题1:redis集群扩容,槽位迁移时,出现超时问题,导致只迁移完成部分

  • redis版本:redis-7.4.0
  • 执行命令:redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-use-empty-masters --cluster-threshold 1 -a 123456
  • 错误信息:clusterManagerMoveSlot: IOERR error or timeout writing to target instance
  • 原因分析:分析的步骤如下
bash 复制代码
# 添加主节点(成功)
[root@centos7 redis-cluster]#redis-cli --cluster add-node 127.0.0.1:6001 172.18.31.17:5001 -a 123456
省略部分日志信息... ...
>>> Getting functions from cluster
>>> Send FUNCTION LIST to 127.0.0.1:6001 to verify there is no functions in it
>>> Send FUNCTION RESTORE to 127.0.0.1:6001
>>> Send CLUSTER MEET to node 127.0.0.1:6001 to make it join the cluster.
[OK] New node added correctly.


# 第一次,执行 rebalance 槽位迁移(提示迁移超时)
[root@centos7 redis-cluster]#redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-use-empty-masters --cluster-threshold 1 -a 123456
省略部分日志信息... ...
[OK] All 16384 slots covered.
>>> Rebalancing across 4 nodes. Total weight = 4.00
Moving 1366 slots from 172.18.31.17:5001 to 127.0.0.1:6001
########################################################################################################################################################################################
Node 172.18.31.17:5001 replied with error:
IOERR error or timeout writing to target instance
*** clusterManagerMoveSlot: IOERR error or timeout writing to target instance


# 第二次,执行 rebalance 槽位迁移(提示5798这个slot处于migrating或importing状态,因此需要修复该slot)
[root@centos7 redis-cluster]#redis-cli --cluster rebalance 127.0.0.1:6001 --cluster-use-empty-masters --cluster-threshold 1 -a 123456
省略部分日志信息... ...
[WARNING] Node 127.0.0.1:6001 has slots in importing state 5798.
[WARNING] Node 172.18.31.17:5001 has slots in migrating state 5798.
[WARNING] The following slots are open: 5798.
>>> Check slots coverage...
[OK] All 16384 slots covered.
*** Please fix your cluster problems before rebalancing


# 按照提示,在rebalance前,先执行fix尝试修复(任然报如下错误)
[root@centos7 redis-cluster]#redis-cli --cluster fix 127.0.0.1:6001 -a 123456
省略部分日志信息... ...
>>> Check for open slots...
[WARNING] Node 127.0.0.1:6001 has slots in importing state 5798.
[WARNING] Node 172.18.31.17:5001 has slots in migrating state 5798.
[WARNING] The following slots are open: 5798.
>>> Fixing open slot 5798
Set as migrating in: 172.18.31.17:5001
Set as importing in: 127.0.0.1:6001
>>> Case 1: Moving slot 5798 from 172.18.31.17:5001 to 127.0.0.1:6001
Moving slot 5798 from 172.18.31.17:5001 to 127.0.0.1:6001: 
Node 172.18.31.17:5001 replied with error:
IOERR error or timeout writing to target instance
  • 解决方案:
bash 复制代码
# 登录相应节点
redis-cli -h 127.0.0.1 -p 6001 -c -a 123456

# 修复有问题的slot
cluster setslot 5798 stable

# 再次执行fix修复操作(可能不需要该步骤,因无法复现迁移超时的问题,暂时先放在这,不影响该问题的解决)
redis-cli --cluster fix 127.0.0.1:5001 -a 123456

# 再次执行槽位迁移(成功)
redis-cli --cluster rebalance 127.0.0.1:5001 -a 123456

参考资料

Redis数据库------主从、哨兵、群集
Redis集群的维护(redis-cli --cluster 命令用法)
Redis6集群安装与运维管理【重要】
全面剖析Redis Cluster原理和应用
Redis官方文档
Redis-3.2.0集群配置(含常见错误)【重要】

相关推荐
莫固执,朋友23 分钟前
Linux下编译 libwebsockets简介和使用示例
linux·websocket·音视频
DCTANT37 分钟前
【合作原创】使用Termux搭建可以使用的生产力环境(八)
linux·debian·idea·termux·vnc·xfce4·termux-x11
开疆智能43 分钟前
ModbusTCP转Profinet:工业通信的利器
linux·服务器·网络
大G哥1 小时前
k8s创建单例redis设置密码
数据库·redis·云原生·容器·kubernetes
秃秃秃秃哇1 小时前
iptables交叉编译(Hisiav300平台)
linux
SUNX-T1 小时前
【conda】Ubuntu 24.04 安装CUDA 12.04
linux·ubuntu·conda
彩虹糖_haha1 小时前
Linux高并发服务器开发 第六天(rwx 对于目录和文件的区别 gcc编译器 动态库静态库)
linux·运维·服务器
A22741 小时前
Redis——双写一致性
java·redis·缓存
omnibots1 小时前
Ubuntu22拉取君正SDK报错
linux
小沈同学呀2 小时前
Redis KEYS查询大批量数据替代方案(推荐SCAN 命令)
redis