Redis -- 12 -- Redis集群搭建

相关文章:


官方文档:Redis cluster tutorial


在搭建 Redis 集群之前,我们先来了解下相关的概念

  • 集群

    • 将多台服务器连接起来共同工作,在某种程度上,可以被看作是一台服务器,集群中单台服务器通常被称为节点
  • 负载均衡

    • 将一台服务器上要处理的请求,根据负载均衡算法分配到其他服务器上去处理,从而减少该台服务器的负载,防止因为负载过大而造成响应超时或宕机等意外情况的发生
  • 分布式

    • 将不同的业务模块部署在不同的服务器上

    • 和集群的区别在于

      • 集群是将同一个业务部署在多台服务器上

      • 分布式是将不同的业务部署在多台服务器上


一、Redis 集群介绍

  • Redis 从 3.0 版本开始支持集群,其是一个提供多个 Redis 节点间共享数据的程序集

  • Redis 集群通过分区来提供一定程度的可用性,体现在实际环境中,当某些节点出现故障时,仍然能继续处理命令;但时如果发生较大的故障 (如:大多数主服务器不可用),集群将停止运行

  • Redis 集群的优势

    • 自动切分数据到不同的节点上

    • 当整个集群的部分节点出现故障或无法与其他节点通信时,仍然能继续处理命令

  • Redis 集群是完全去中心化的,不存在中心节点或代理节点

  • Redis 集群没有统一的入口,当客户端连接集群时,只需要连接集群中的任意节点即可,集群内部的节点是相互通信的 (ping-pong 机制),每个节点都是一个 Redis 实例


二、Redis 集群 TCP 端口

  • 每个 Reids 集群需要建立两个 TCP 连接,监听两个端口

    • 客户端端口

      • 通常是 6379,用于与客户端通信,需要对所有客户端和集群节点开放

      • 集群节点需要通过该端口向客户端转义数据

    • 集群总线端口

      • 通常是 6379 + 10000,用于节点之间通过二进制协议进行通信,只需要对集群节点开发

      • 集群中各节点通过集群总线进行检测故障节点、更新配置等操作


三、Redis 集群数据分片

  • Redis 集群没有使用一致性 Hash 算法, 而是引入了哈希槽的概念

  • Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后再对 16384 进行取模来决定放入哪个槽中

  • Redis 集群中每个节点负责一部分哈希槽,举例说明,假如当前集群有 3 个节点 A、B、C,那么

    • 节点 A 的哈希槽范围为:[0-5500]

    • 节点 B 的哈希槽范围为:[5501-11000]

    • 节点 C 的哈希槽范围为:[11001-16384]

  • 通过哈希槽的结构,我们可以很轻松地添加或删除集群中的节点

    • 如果要添加节点 D,只需要将节点 A、B、C 的部分哈希槽移动到节点 D 即可

    • 如果要删除节点 A,只需要将节点 A 的哈希槽移动到节点 B、C,然后将节点 A 从集群中移除即可

    • 由于将哈希槽从一个节点移动到另一个节点不需要停止操作,所以无论添加删除或改变某个节点哈希槽的数量,都不会造成集群无法使用


四、Redis 集群主从模型

  • 为了当部分节点失败或大部分节点无法通信的情况下集群仍然可用,集群使用了主从模型,每个哈希槽具有 1 到 N 个副本

  • 在上面提到的例子中 (具有 A、B、C 3 个节点的集群),假如节点 B 失败,那么集群将会因为缺少 [5501-11000] 范围的哈希槽而不可用

  • 但是如果我们在创建集群时,为 Master A、B、C 分别添加一个 Slave A1、B1、C1,即创建了一个 3 主 3 从的集群,这样的话即使节点 B 失效,集群也会选举节点 B1 作为新的 Master 来提供服务,那么集群便不会因为缺少 [5501-11000] 范围的哈希槽而不可用

  • 但需要注意的是,如果节点 B 和 B1 都失败了,集群将会因为缺少 [5501-11000] 范围的哈希槽而不可用


五、Redis 集群一致性保证

  • Redis 集群无法保证数据的强一致性,在以下情况下,Redis 集群可能会丢失客户端的写入操作

    • 使用了异步复制

      • 所谓异步复制,是指主从节点之间采用异步的方式来同步数据

      • 流程如下

        1. 客户端向 Master B 发起写操作

        2. Master B 回应客户端写操作成功

        3. Master B 向它的 Slave B1、B2、B3 同步该写操作

      • 当 Master B 回应客户端写操作成功时,并不会等待 Slave B1、B2、B3 确认后再回复,因为这会对 Redis 的性能造成影响

      • 如果 Master B 在回应客户端写操作成功后,在发送写操作到 Slave B1、B2、B3 前发生故障宕机,随后在 Slave B1、B2、B3 (未收到写操作) 中会选举出一个新的 Master,那么之前的写操作就永远丢失了

      • 因此我们需要根据实际的业务需求,在性能和一致性之间进行权衡

    • 在网络分区期间,客户端与少数实例 (至少包括一个 Master) 隔离

      • 举例说明

        • 假如当前集群有 6 个节点 A、B、C、A1、B1、C1,其中 A、B、C 为 Master,A1、B1、C1 为 Slave,另外还有个一个客户端 Z1

        • 在发生网络分区后,可能在分区的一侧有节点 A,C,A1,B1,C1,分区的另一侧有节点 B、Z1

        • 此时 Z1 仍然能向 Master B 发起写操作,如果网络分区时间很短,当分区恢复后,集群可以继续正常工作;如果网络分区时间很长,在另一边会认为 Master B 已经挂了,于是选举节点 B1 作为新的 Master,当分区恢复后,由于节点 B1 变为了 Master,节点 B 则会变成节点 B1 的 Slave,节点 B 会清除自身的数据去同步节点 B1 的数据,因此会丢失之前 Z1 向节点 B 发起的写操作

      • 这就是所谓的 Redis 集群脑裂,可以通过设置以下两个参数来尽量避免数据的丢失

        • min-replicas-to-write

          • 表示 Slave 连接到 Master 的最小数量,默认为 0 (0 表示禁用)
        • min-replicas-max-lag

          • 表示 Slave 连接到 Master 的最大延迟时间,默认为 10 s
        • 假如 min-replicas-to-write 为 3,min-replicas-max-lag 为 10,则表示一个 Master 至少需要 3 个 Slave,且这 3 个 Slave 连接到 Master 的时间不能超过 10 s,否则 Master 将停止写入


六、Redis 集群配置参数

  • cluster-enabled <yes/no>

    • 集群开关,默认为 false
  • cluster-config-file <filename>

    • 节点集群配置文件,该文件由节点自身维护
  • cluster-node-timeout <milliseconds>

    • 访问节点超时时间,默认为 15000 ms
  • cluster-replica-validity-factor <factor>

    • 在进行故障转移时,Slave 会申请成为 Master,但是有些 Slave 可能与 Master 断开连接的时间过长,导致数据过于陈旧,这样的 Slave 不应该被提升为 Master,该参数主要用于判断 Slave 和 Master 断线的时间是否过长

    • 如果为 0,则表示集群始终会进行主从切换,而不管主从节点之间断开的时间长短

    • 如果为正数,则比较断开的时间和 (cluster-node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period 的大小,假设 node-timeout 为 30 s、replica-validity-factor 为 10 s、repl-ping-replica-period 为 10 s,那么如果断开时间超过 310 s,Slave 将不会尝试进行故障转移

  • cluster-migration-barrier <count>

    • 当 Master 拥有的 Slave 数量大于该值时,Slave 才能迁移到其他的 Master 上,默认为 1
  • cluster-require-full-coverage <yes/no>

    • 默认情况下,集群中的所有哈希槽都有节点负责,集群才能正常运行,默认为 yes;当设置为 no 时,可以在哈希槽没有完全分配的情况下正常运行

七、搭建 Redis 集群

  • 为了实现 Redis 集群的高可用,我们至少需要 6 个节点 (3 主 3 从),这里我们在一台服务器上运行 6 个 Redis 实例,来搭建一个伪集群,修改端口号为 (6380 - 6385)

  • 搭建流程如下

    • 1、在服务器上搭建一个单机版的 Redis,具体流程参考 --> Redis – 02 – Linux上源码包安装Redis

    • 2、在 /usr/local/ 目录下新建 redis-cluster 目录,用于存放集群节点

      • cd /usr/local/

      • mkdir redis-cluster

    • 3、将第 1 步安装完的 Redis 的 bin 目录下的所有文件复制到 /usr/local/redis-cluster/redis01/ 目录下

      • cp -r /usr/local/redis/bin/ /usr/local/redis-cluster/redis01/
    • 4、修改 /usr/local/redis-cluster/redis01/ 目录下的 redis.conf 配置文件

      • vim /usr/local/redis-cluster/redis01/redis.conf

        # 将 bind 127.0.0.1 一行注释掉
        # bind 127.0.0.1
        
        # 设置端口
        port 6380
        
        # 开启守护进程
        daemonize yes
        
        # 设置 pid 文件
        pidfile /var/run/redis-6380.pid
        
        # 设置 log 文件
        logfile "./redis-6380.log"
        
        # 设置工作目录
        dir ./
        
        # 设置 Master 密码
        masterauth 123456
        
        # 设置密码
        requirepass 123456
        
        # 开启集群模式
        cluster-enabled yes
        
        # 设置节点集群配置文件
        cluster-config-file nodes-6380.conf
        
        # 设置访问节点超时时间
        cluster-node-timeout 15000
        
      • :wq

    • 5、在 /usr/local/redis-cluster/redis01/ 目录下编写启动脚本

      • vim /usr/local/redis-cluster/redis01/start.sh

        #!/bin/bash
        
        #实例路径
        APP_NAME=/usr/local/redis-cluster/redis01/redis-server
        #配置文件路径
        APP_CONF=/usr/local/redis-cluster/redis01/redis.conf
        
        #使用说明,用来提示输入参数
        usage() {
        	echo "Usage: run.sh {start|stop|restart|status}"
        	exit 1
        }
        
        #检查程序是否正在运行
        is_running() {
        	pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}'`
        	#如果不存在则返回1,存在则返回0
        	if [ -z "${pid}" ];
        	then
        		return 1
        	else
        		return 0
        	fi
        }
        
        #启动
        start() {
        	is_running
        	if [ $? -eq 0 ];
        	then
        		echo "${APP_NAME} is already running, pid=${pid}"
        	else
        		${APP_NAME} ${APP_CONF}
        	fi
        }
        
        #停止
        stop() {
        	is_running
        	if [ $? -eq 0 ];
        	then
        		kill -9 $pid
        	else
        		echo "${APP_NAME} is not running"
        	fi
        }
        
        #查看运行状态
        status() {
        	is_running
        	if [ $? -eq 0 ];
        	then
        		echo "${APP_NAME} is running, pid is ${pid}"
        	else
        		echo "${APP_NAME} is not running"
        	fi
        }
        
        #重启
        restart() {
        	stop
        	sleep 5
        	start
        }
        
        #根据输入的参数,选择对应的执行方法,不输入则执行使用说明
        case "$1" in
        	start)
        		start
        		;;
        	stop)
        		stop
        		;;
        	status)
        		status
        		;;
        	restart)
        		restart
        		;;
        	*)
        		usage
        		;;
        esac
        
      • :wq

      • chmod +x /usr/local/redis-cluster/redis01/start.sh

    • 6、将 /usr/local/redis-cluster/redis01/ 目录下的所有文件复制 5 份到 /usr/local/redis-cluster/(redis02 - redis06)/ 目录下

      • cp -r /usr/local/redis-cluster/redis01/ /usr/local/redis-cluster/redis02/

      • cp -r /usr/local/redis-cluster/redis01/ /usr/local/redis-cluster/redis03/

      • cp -r /usr/local/redis-cluster/redis01/ /usr/local/redis-cluster/redis04/

      • cp -r /usr/local/redis-cluster/redis01/ /usr/local/redis-cluster/redis05/

      • cp -r /usr/local/redis-cluster/redis01/ /usr/local/redis-cluster/redis06/

      • 在 redis02 - redis06 五个文件中,修改 redis.conf 配置文件和 start.sh 启动脚本

        • redis.conf 配置文件

          • port 分别改为 6381 - 6385

          • pidfile 分别改为 /var/run/redis-(6381 - 6385).pid;

          • logfile 分别改为 ./redis-(6381 - 6385).log

          • cluster-config-file 分别改为 nodes-(6381 - 6385).conf

        • start.sh 启动脚本

          • redis01 分别改为 redis02 - redis06
    • 7、在 /usr/local/redis-cluster/ 目录下编写总的启动脚本和总的关闭脚本

      • vim /usr/local/redis-cluster/start.sh

        #!/bin/bash
        
        #项目路径
        REDIS01_PATH=/usr/local/redis-cluster/redis01
        REDIS02_PATH=/usr/local/redis-cluster/redis02
        REDIS03_PATH=/usr/local/redis-cluster/redis03
        REDIS04_PATH=/usr/local/redis-cluster/redis04
        REDIS05_PATH=/usr/local/redis-cluster/redis05
        REDIS06_PATH=/usr/local/redis-cluster/redis06
        
        #使用说明,用来提示输入参数
        usage() {
        	echo "Usage: start.sh {all|redis01|redis02|redis03|redis04|redis05|redis06}"
        	exit 1
        }
        
        #启动所有
        all() {
        	cd ${REDIS01_PATH}
        	./start.sh start
        
        	cd ${REDIS02_PATH}
        	./start.sh start
        	
        	cd ${REDIS03_PATH}
        	./start.sh start
        	
        	cd ${REDIS04_PATH}
        	./start.sh start
        	
        	cd ${REDIS05_PATH}
        	./start.sh start
        	
        	cd ${REDIS06_PATH}
        	./start.sh start
        }
        
        #启动redis01
        redis01() {
        	cd ${REDIS01_PATH}
        	./start.sh start
        }
        
        #启动redis02
        redis02() {
        	cd ${REDIS02_PATH}
        	./start.sh start
        }
        
        #启动redis03
        redis03() {
        	cd ${REDIS03_PATH}
        	./start.sh start
        }
        
        #启动redis04
        redis04() {
        	cd ${REDIS04_PATH}
        	./start.sh start
        }
        
        #启动redis05
        redis05() {
        	cd ${REDIS05_PATH}
        	./start.sh start
        }
        
        #启动redis06
        redis06() {
        	cd ${REDIS06_PATH}
        	./start.sh start
        }
        
        #根据输入的参数,选择对应的执行方法,不输入则执行使用说明
        case "$1" in
        	all)
        		all
        		;;
        	redis01)
        		redis01
        		;;
        	redis02)
        		redis02
        		;;
        	redis03)
        		redis03
        		;;
        	redis04)
        		redis04
        		;;
        	redis05)
        		redis05
        		;;
        	redis06)
        		redis06
        		;;
        	*)
        		usage
        		;;
        esac
        
      • :wq

      • chmod +x /usr/local/redis-cluster/start.sh

      • vim /usr/local/redis-cluster/stop.sh

        #!/bin/bash
        
        #项目路径
        REDIS01_PATH=/usr/local/redis-cluster/redis01
        REDIS02_PATH=/usr/local/redis-cluster/redis02
        REDIS03_PATH=/usr/local/redis-cluster/redis03
        REDIS04_PATH=/usr/local/redis-cluster/redis04
        REDIS05_PATH=/usr/local/redis-cluster/redis05
        REDIS06_PATH=/usr/local/redis-cluster/redis06
        
        #使用说明,用来提示输入参数
        usage() {
        	echo "Usage: stop.sh {all|redis01|redis02|redis03|redis04|redis05|redis06}"
        	exit 1
        }
        
        #关闭所有
        all() {
        	cd ${REDIS01_PATH}
        	./start.sh stop
        
        	cd ${REDIS02_PATH}
        	./start.sh stop
        	
        	cd ${REDIS03_PATH}
        	./start.sh stop
        	
        	cd ${REDIS04_PATH}
        	./start.sh stop
        	
        	cd ${REDIS05_PATH}
        	./start.sh stop
        	
        	cd ${REDIS06_PATH}
        	./start.sh stop
        }
        
        #关闭redis01
        redis01() {
        	cd ${REDIS01_PATH}
        	./start.sh stop
        }
        
        #关闭redis02
        redis02() {
        	cd ${REDIS02_PATH}
        	./start.sh stop
        }
        
        #关闭redis03
        redis03() {
        	cd ${REDIS03_PATH}
        	./start.sh stop
        }
        
        #关闭redis04
        redis04() {
        	cd ${REDIS04_PATH}
        	./start.sh stop
        }
        
        #关闭redis05
        redis05() {
        	cd ${REDIS05_PATH}
        	./start.sh stop
        }
        
        #关闭redis06
        redis06() {
        	cd ${REDIS06_PATH}
        	./start.sh stop
        }
        
        #根据输入的参数,选择对应的执行方法,不输入则执行使用说明
        case "$1" in
        	all)
        		all
        		;;
        	redis01)
        		redis01
        		;;
        	redis02)
        		redis02
        		;;
        	redis03)
        		redis03
        		;;
        	redis04)
        		redis04
        		;;
        	redis05)
        		redis05
        		;;
        	redis06)
        		redis06
        		;;
        	*)
        		usage
        		;;
        esac
        
      • :wq

      • chmod +x /usr/local/redis-cluster/stop.sh

    • 8、启动所有 Redis 实例

      • /usr/local/redis-cluster/start.sh all
    • 9、查看 Reids 实例进程

      • ps -ef | grep redis

        在这里插入图片描述

    • 10、将第 1 步安装完的 Redis 的 bin 目录下的 redis-cli 文件复制到 /usr/local/redis-cluster/ 目录下

      • cp /usr/local/redis/bin/redis-cli /usr/local/redis-cluster/
    • 11、创建 Redis 集群

      • redis-cli -a 123456 --cluster create 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 --cluster-replicas 1

      • 注意:如果使用的是外网 IP,且使用的是阿里云的服务器,记的在安全组中配置相关端口 (6380 - 6385)

      • 在命令执行中途,有地方需要手动输入,输入 yes 即可

        在这里插入图片描述

      • 如上所示,展示了 Redis 集群为每个节点分配的哈希槽,总共有 6 个节点,其中 3 个为 Master,分别映射了 [0-5460][5461-10922][10923-16383] 区间的哈希槽;另外 3 个为 Slave,分配的哈希槽为 0

    • 12、查看 Redis 集群信息

      • /usr/local/redis-cluster/redis-cli -a 123456 -p 6380

        在这里插入图片描述

      • 如上所示,端口为 6380、6381、6382 的 Redis 实例为 Master,其分别对应端口为 6384、6385、6383 的 Slave,主从比例为 1:1,即创建集群时附带的比例参数 --cluster-replicas 1

      • 通过这种方式创建的主从对应关系是随机的

    • 13、至此,Redis 集群就搭建成功了


四、Redis 集群测试

  • 数据分配 + 主从同步测试

    • Master 测试

      • 连接 Redis 集群 Master

        • /usr/local/redis-cluster/redis-cli -a 123456 -p 6380 -c

        • 注意:此处一定要加上 -c,否则节点之间无法自动跳转

      • 进行测试

        在这里插入图片描述

      • 测试结果

        在这里插入图片描述

        • 由上可知,数据会被均匀地分配到不同的 Master 中

        • Redis 集群自动开启主从同步,Slave 会自动同步 Master 中的数据

    • Slave 测试

      • 连接 Redis 集群 Slave

        • /usr/local/redis-cluster/redis-cli -a 123456 -p 6385 -c
      • 进行测试

        在这里插入图片描述

      • 测试结果

        在这里插入图片描述

      • 由测试结果可知,在端口为 6385 的 Slave 中写入数据,不会将数据保存在该 Slave 中,而是根据 key 对 16384 进行取模的值来决定放入哪个哈希槽中,此处放入的哈希槽为 [12539],位于端口为 6382 的 Master 中,也就是说,该数据会被保存到该 Master 中,并自动同步到端口为 6383 的 Slave 中

      • 注意:Redis 集群通常做读写分离,也就是 Master 负责写操作,Slave 负责读操作,此处在 Slave 中进行写操作,只是为了测试起见

  • 故障转移测试

    • 为了方便测试,我们修改下 redis01 - redis06 的配置文件

      # 将访问节点超时时间设为 5 s
      cluster-node-timeout 5000
      
    • 关闭 Master,模拟 Master 宕机

      • /usr/local/redis-cluster/stop.sh redis01
    • 查看 Redis 日志

      • tail -f /usr/local/redis-cluster/redis05/redis-6384.log

        在这里插入图片描述

    • 查看 Redis 集群信息

      • /usr/local/redis-cluster/redis-cli -a 123456 -p 6384

        在这里插入图片描述

      • 如上所示,原来端口为 6380 的 Master,现在被标记为了 fail 状态,而原来端口为 6384 的 Slave 则变为了新的 Master,故障转移成功

    • 开启 Master,模拟 Master 恢复

      • /usr/local/redis-cluster/start.sh redis01
    • 查看 Redis 日志

      • tail -f /usr/local/redis-cluster/redis01/redis-6380.log

        在这里插入图片描述

    • 查看 Redis 集群信息

      • tail -f /usr/local/redis-cluster/redis05/redis-6384.log

        在这里插入图片描述

      • 如上所示,原来端口为 6380 的 Master,现在已变为了端口为 6384 的 Master 的 Slave


五、参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值