Day12_08_Redis教程之Redis集群原理及集群搭建

(71) 2024-05-21 11:01:01

Redis集群原理及集群搭建

一. Redis集群简介

1. 为什么要搭建redis集群?

为了提高网站的响应速度,我们可以把经常用到的数据放到内存中进行缓存,而不是放到数据库中进行持久化.Redis是一个很好的Cache工具,当然了还有Memcached.在我们的分布式系统中,热点数据往往巨大,比如单点登录、用户浏览商品的信息、用户信息、用户收藏的商品信息、短信提醒数据等,也都用到了redis.如何使redis可以横向可伸缩扩展,这需要由多台机器协同提供服务,一台挂掉了,另一台马上顶上去,即分布式的redis集群,就对提升系统的性能非常重要.

2. redis-cluster架构图

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第1张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第2张

3. redis-cluster集群容错功能

Redis集群在启动的时候就自动在多个节点间分好片,同时提供了分片之间的可用性:当一部分redis节点故障或网络中断,集群也能继续工作.但是,当大面积的节点故障或网络中断(比如大部分的主节点都不可用了),集群就不能使用了.

所以,从实用性的角度来看,Redis集群提供以下功能:

  • 1️⃣.自动把数据切分到多个Redis节点中;

  • 2️⃣.当一部分节点挂了或不可达,集群依然能继续工作.

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第3张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第4张

4. Redis集群的主从模式

为了保证在部分节点故障或网络不通时集群依然能正常工作,集群使用了主从模型,每个哈希槽有一主节点 到 N个副本 (N-1个从节点). 在我们刚才的集群例子中,有A,B,C三个节点,如果B节点故障集群就不能正常工作了,因为B节点中的哈希槽数据没法操作.但是,如果我们给每一个节点都增加一个从节点,就变成了: A,B,C三个节点是主节点, A1, B1, C1 分别是他们的从节点,当B节点宕机时,我们的集群也能正常运作. B1节点是B节点的副本,如果B节点故障,集群会提升B1为主节点,从而让集群继续正常工作.但是,如果B和B1同时故障,集群就不能继续工作了.

5. 架构细节

1️⃣.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;

2️⃣.节点的fail是当集群中超过半数的节点检测失效时才生效;

3️⃣.客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群中所有的节点,连接集群中任何一个可用节点即可;

4️⃣.redis-cluster会把所有的物理节点映射到[0-16383]个slot哈希槽上,cluster负责维护哈希槽里的值.Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点.

6. Redis集群的几个重要特征

1️⃣.Redis 的集群分片是将key所在的空间拆分成16384个槽位,每一个节点负责其中一部分槽位;

2️⃣.Redis提供一定程度的可用性,可以在某个节点宕机或者不可达的情况下继续处理命令;

3️⃣.Redis 集群中不存在中心(central)节点或者代理(proxy)节点,集群的其中一个主要设计目标是达到线性可扩展性(linear scalability);

4️⃣.Redis集群要想正常工作,必须要三个以上的主节点,在我们搭建的redis集群环境中,三个主节点每个都需要有一个从节点,所以一共有六个节点,也就是说需要开启6个redis服务.

7. Redis集群数据的分片

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第5张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第6张

Redis集群不是使用一致性哈希,而是使用哈希槽,整个Redis集群有16384个哈希槽.决定一个key应该分配到哪个槽的算法是:计算该key的CRC16结果再模16384 .

集群中的每个节点负责一部分哈希槽,比如集群中有3个节点,则:

  • 节点A存储的哈希槽范围是: 0 – 5500;

  • 节点B存储的哈希槽范围是:5501 – 11000;

  • 节点C存储的哈希槽范围是: 11001 – 16384.

这样的分布方式方便节点的添加和删除.比如,此时如果需要新增一个节点D,只需要把A、B、C中的部分哈希槽数据移到D节点.同样,如果希望在集群中删除A节点,只需要把A节点上哈希槽的数据移到B和C节点,当A节点的数据全部被移走后,A节点就可以完全从集群中删除.

因为把哈希槽从一个节点移到另一个节点是不需要停机的,所以增加或删除节点,或更改节点上的哈希槽,也是不需要停机的.

如果多个key都属于一个哈希槽,集群支持通过一个命令(或事务,或lua脚本)同时操作这些key.通过“哈希标签”的概念,用户可以让多个key分配到同一个哈希槽.哈希标签在集群详细文档中有描述,这里做个简单介绍: 如果key含有大括号”{}”,则只有大括号中的字符串会参与哈希,比如”this{foo}”和”another{foo}”这2个key会分配到同一个哈希槽,所以可以在一个命令中同时操作他们.

8. Redis集群的TCP端口

Redis集群中的每个节点都需要建立2个TCP连接,所以需要监听2个端口: 一个端口称之为“客户端端口”,用于接受客户端指令,与客户端交互,比如6379;另一个端口称之为“集群总线端口”,是在客户端端口号上加10000,比如16379,用于节点之间通过二进制协议通讯.

各节点通过集群总线检测宕机节点、更新配置、故障转移验证等.客户端只能使用客户端端口,不能使用集群总线端口.请确保你的防火墙允许打开这两个端口,否则Redis集群没法工作.客户端端口和集群总线端口之间的差值是固定的,集群总线端口比客户端端口高10000.

注意,关于集群的2个端口:

  • 1️⃣.客户端端口(一般是6379)需要对所有客户端和集群节点开放,因为集群节点需要通过该端口转移数据;

  • 2️⃣.集群总线端口(一般是16379)只需对集群中的所有节点开放.

这2个端口必须打开,否则集群没法正常工作.

集群节点之间通过集群总线端口交互数据,使用的协议不同于客户端的协议,是二进制协议,这可以减少带宽和处理时间.

9. Redis集群的一致性保证

Redis集群不能保证强一致性.一些已经向客户端确认写成功的操作,会在某些不确定的情况下丢失.

产生写操作丢失的第一个原因,是因为主从节点之间使用了异步的方式来同步数据.

一个写操作是这样一个流程:

  • 1️⃣.客户端向主节点B发起写的操作;

  • 2️⃣.主节点B回应客户端写操作成功;

  • 3️⃣.主节点B向它的从节点B1,B2,B3同步该写操作.

从上面的流程可以看出来,主节点B并没有等从节点B1,B2,B3写完之后再回复客户端这次操作的结果.所以,如果主节点B在通知客户端写操作成功之后,但同步给从节点之前,主节点B故障了,其中一个没有收到该写操作的从节点会晋升成主节点,该写操作就这样永远丢失了.

就像传统的数据库,在不涉及到分布式的情况下,它每秒写回磁盘.为了提高一致性,可以在写盘完成之后再回复客户端,但这样就要损失性能.这种方式就等于Redis集群使用同步复制的方式.

基本上,在性能和一致性之间,需要一个权衡.

如果真的需要,Redis集群支持同步复制的方式,通过WAIT指令来实现,这可以让丢失写操作的可能性降到很低.但就算使用了同步复制的方式,Redis集群依然不是强一致性的.在某些复杂的情况下,比如从节点在与主节点失去连接之后被选为主节点,不一致性还是会发生.

这种不一致性发生的情况是这样的,当客户端与少数的节点(至少含有一个主节点)网络联通,但他们与其他大多数节点网络不通.比如6个节点,A,B,C是主节点,A1,B1,C1分别是他们的从节点,一个客户端称之为Z1.

当网络出问题时,他们被分成2组网络,组内网络联通,但两个组之间的网络不通.假设A/B/C,A1/B1/C1彼此之间是联通的,另一边,B和Z1的网络是联通的.Z1可以继续往B发起写操作,B也接受Z1的写操作.当网络恢复时,如果这个时间间隔足够短,集群仍然能继续正常工作.如果时间比较长,以致B1在大多数的这边被选为主节点,那刚才Z1发给B的写操作都将丢失.

注意,Z1给B发送写操作是有一个限制的,如果时间长度达到了大多数节点那边可以选出一个新的主节点时,少数这边的所有主节点都不接受写操作.

这个时间的配置,称之为节点超时(node timeout),对集群来说非常重要,当达到了这个节点超时的时间之后,主节点被认为已经宕机,可以用它的一个从节点来代替.同样,在节点超时时,如果主节点依然不能联系到其他主节点,它将进入错误状态,不再接受写操作.

二. Redis集群运行原理

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第7张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第8张

redis cluster在设计的时候,就考虑到了去中心化,去中间件.也就是说,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态.每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据.

Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽(hash slot)的方式来分配数据.redis cluster 默认分配了 16384 个slot,当我们设置一个 key 的时候,redis集群会先用CRC16算法对key计算出一个结果,然后利用该结果来对16384取模得到所属的slot位,最后将这个key 分到哈希槽区间所在的节点上.具体算法就是: CRC16(key) % 16384 .所以我们在测试的时候看到set 和 get 的时候,直接跳转到了7001端口所在的节点.

Redis 集群会把数据存在一个 master 节点上,然后在这个 master 节点和其对应的 slave 节点之间进行数据同步.当读取数据时,会根据一致性哈希算法得到对应的 master 节点来获取数据,只有当一个master 挂掉之后,才会启动一个对应的 slave 节点,将其提升为master .

注意

必须要创建3个或以上的主节点,否则在创建集群时会失败,并且当存活的主节点数小于总节点数的一半时,整个集群就无法提供服务了.

三. Redis集群的搭建

Redis集群中至少应该有三个节点,要保证集群的高可用,需要每个节点有一个备份机,所以一个Redis集群至少需要6台服务器.

搭建伪分布式集群,可以使用一台虚拟机运行6个redis实例,然后修改redis的端口号7001-7006(自定义端口,只要不和其他程序重复).

四. Redis集群搭建步骤

1. Redis集群需求

一般情况下,我们需要6台redis服务器,搭建出一个分布式的redis集群.在我们的案例中,是用一台虚拟机模拟出6个节点,分别创建出3个master节点,3个slave节点,这几个服务器分别运行在不同的端口7001-7006上,以此构建出一个分布式的伪集群环境.

2. Redis集群参数配置

介绍一下redis集群中redis.conf文件中的常用参数.

  • cluster-enabled :如果配置”yes”则开启集群功能,此redis实例作为集群的一个节点,否则,它是一个普通的单一的redis实例;

  • cluster-config-file :注意:虽然此配置的名字叫“集群配置文件”,但是此配置文件不能人工编辑,它是集群节点自动维护的文件,主要用于记录集群中有哪些节点、他们的状态以及一些持久化参数等,方便在重启时恢复这些状态.通常是在收到请求之后这个文件就会被更新.

  • cluster-node-timeout : 这是集群中的节点能够失联的最大时间,超过这个时间,该节点就会被认为故障.如果主节点超过这个时间还是不可达,则用它的从节点将启动故障迁移,升级成主节点.注意,任何一个节点在这个时间之内如果还是没有连上大部分的主节点,则此节点将停止接收任何请求.

  • cluster-slave-validity-factor : 如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节点.如果设置成正数,则cluster-node-timeout乘以cluster-slave-validity-factor得到的时间,是从节点与主节点失联后,此从节点数据有效的最长时间,超过这个时间,从节点不会启动故障迁移.假设cluster-node-timeout=5,cluster-slave-validity-factor=10,则如果从节点跟主节点失联超过50秒,此从节点不能成为主节点.注意,如果此参数配置为非0,将可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作.在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作.

  • cluster-migration-barrier :主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移.更详细介绍可以看本教程后面关于副本迁移到部分.

  • cluster-require-full-coverage :在部分key所在的节点不可用时,如果此参数设置为”yes”(默认值),则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读操作.

3. 创建出6个redis节点

在/url/local/目录下创建一个redis-cluster目录.

mkdir redis-cluster

然后在redis-cluster目录下,创建出名为redis01,redis02,redis03,redis04,redis05,redis06的6个目录,并将/etc/redis/redis.conf这个配置文件拷贝到这6个目录中.

#切换到redis-cluster目录
cd /usr/local/redis-cluster

#创建6个文件夹
mkdir redis01 redis02 redis03 redis04 redis05 redis06

#切换到/etc/redis/目录
cd /etc/redis/

#复制redis.conf配置文件
cp redis.conf redis-cluster/redis01/
cp redis.conf redis-cluster/redis02/
cp redis.conf redis-cluster/redis03/
cp redis.conf redis-cluster/redis04/
cp redis.conf redis-cluster/redis05/
cp redis.conf redis-cluster/redis06/

 

4. 分别修改这6个配置文件,主要修改如下内容

1️⃣. redis01/redis.conf内容

port 7001
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes01.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

2️⃣. redis02/redis.conf内容

port 7002
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes02.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

3️⃣. redis03/redis.conf内容

port 7003
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes03.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

4️⃣. redis04/redis.conf内容

port 7004
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes04.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

5️⃣. redis05/redis.conf内容

port 7005
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes05.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

6️⃣. redis06/redis.conf内容

port 7006
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes06.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes

5. 启动各个节点(了解)

一个节点一个节点的启动命令:

sudo redis-server /usr/local/redis-cluster/redis01/redis.conf

sudo redis-server /usr/local/redis-cluster/redis02/redis.conf

sudo redis-server /usr/local/redis-cluster/redis03/redis.conf
 
sudo redis-server /usr/local/redis-cluster/redis04/redis.conf

sudo redis-server /usr/local/redis-cluster/redis05/redis.conf

sudo redis-server /usr/local/redis-cluster/redis06/redis.conf 

如果我们一个节点一个节点进行启动,会特别麻烦,所以我们接下来创建一个脚本文件,用这个脚本文件同时启动所有的redis节点.

6. 创建启动所有节点的脚本

为了方便每个redis的启动,可以创建启动redis服务的shell脚本.

#切换到该目录
cd /usr/local/redis-cluster/

#创建一个文件
touch start-all-redis.sh

#注意:以下内容是shell脚本的内容.

#!/bin/sh
redis-server /usr/local/redis-cluster/redis01/redis.conf
redis-server /usr/local/redis-cluster/redis02/redis.conf
redis-server /usr/local/redis-cluster/redis03/redis.conf
redis-server /usr/local/redis-cluster/redis04/redis.conf
redis-server /usr/local/redis-cluster/redis05/redis.conf
redis-server /usr/local/redis-cluster/redis06/redis.conf

#更改shell脚本的权限
chmod +x start-all-redis.sh

7. 创建关闭所有redis服务的shell脚本

#切换到redis-cluster目录
cd /usr/local/redis-cluster/

#创建一个脚本文件
touch stop-all-redis.sh

#!/bin/sh
redis-cli -h 127.0.0.1 -p 7001 shutdown
redis-cli -h 127.0.0.1 -p 7002 shutdown
redis-cli -h 127.0.0.1 -p 7003 shutdown
redis-cli -h 127.0.0.1 -p 7004 shutdown
redis-cli -h 127.0.0.1 -p 7005 shutdown
redis-cli -h 127.0.0.1 -p 7006 shutdown

#更改shell脚本的权限
chmod +x stop-all-redis.sh

8. 安装集群所需的ruby环境

8.1 使用ruby脚本搭建集群,需要ruby的运行环境.

apt-get install ruby
apt-get install rubygems

8.2 安装ruby脚本运行使用的包

gem install redis

9. 利用redis-trib.rb创建集群

Redis 官方提供了redis-trib.rb这个工具,使用下面这个命令即可完成redis集群安装.

9.1 查找redis-trib.rb文件所在位置

find / -name "redis-trib.rb"

发现redis-trib.rb文件在/usr/share/doc/redis-tools/examples/redis-trib.rb位置上.

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第9张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第10张

9.2 把redis-trib.rb复制到/usr/local/redis-cluster/目录下

#切换目录
cd /usr/share/doc/redis-tools/examples/

#复制redis-trib.rb文件
cp redis-trib.rb /usr/local/redis-cluster/

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第11张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第12张

9.3 redis-trib.rb创建集群

./redis-trib.rb  create  --replicas  1  127.0.0.1:7001 127.0.0.1:7002  127.0.0.1:7003 127.0.0.1:7004  127.0.0.1:7005  127.0.0.1:7006

注意:安装过程中需要输入 yes ,然后出现如下内容,说明安装成功.

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第13张Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第14张

当集群安装好之后,通过我们之前创建的redis集群启动脚本,开启redis集群节点.

./start-all-redis.sh

10. 检查redis启动情况

ps -ef | grep redis

root      61020      1  0 02:14 ?        00:00:01 redis-server 127.0.0.1:7001 [cluster]    
root      61024      1  0 02:14 ?        00:00:01 redis-server 127.0.0.1:7002 [cluster]    
root      61029      1  0 02:14 ?        00:00:01 redis-server 127.0.0.1:7003 [cluster]    
 
#监听端口 
netstat -tnlp | grep redis
tcp        0      0 127.0.0.1:17001             0.0.0.0:*                   LISTEN      61020/redis-server 
tcp        0      0 127.0.0.1:17002             0.0.0.0:*                   LISTEN      61024/redis-server 
tcp        0      0 127.0.0.1:17003             0.0.0.0:*                   LISTEN      61029/redis-server 
tcp        0      0 127.0.0.1:7001             0.0.0.0:*                   LISTEN      61020/redis-server 
tcp        0      0 127.0.0.1:7002              0.0.0.0:*                   LISTEN      61024/redis-server
tcp        0      0 127.0.0.1:7003              0.0.0.0:*                   LISTEN      61029/redis-server

11. 集群验证

比如在第一台机器上连接集群的7003端口所在的节点,在另外一台机器上连接7005节点,连接方式为 redis-cli -h 127.0.0.1 -c -p 7003,加参数 -c 可连接到集群,因为上面 redis.conf 将 bind 改为了ip地址,所以 -h 参数不可以省略.

注意:一定要添加 -c 参数,才能连接到redis集群!!!

Day12_08_Redis教程之Redis集群原理及集群搭建 (https://mushiming.com/)  第15张

说明集群运作正常.

五. 集群搭建过程中可能出现的问题

1. Could not connect to Redis at 127.0.0.1:6379: Connection refused

解决办法: 检查端口号是否有错,我一开始创建的redis文件夹,并有把redis.conf下的端口号改为7000,但是测试的时候只能连接6379的端口,明明是修改过了,所以个人猜想,可能redis这个文件名,系统会默认打开6379端口,所以我改名为redis1,端口号改为7001;

2. 执行set命名,出现ERR Slot 0 is already busy(Redis::CommandError)

解决办法: 杀死改redis进程, 用./redis.conf 配置文件方式登录这个节点,然后执行flushall和cluster reset即可,然后重新执行群集脚本命令: ./redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006

3. 第二次开启redis集群时,就不用再使用这个命令

./redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006

再创建集群,否则会出这个错:
[ERR] Node 127.0.0.1:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

说明上次创建的集群还在,却不为空,直接redis1/redis-cli -h 127.0.0.1 -p 7002 -c进入连接即可.

如果你要重新创建一个新的集群,就要进去上次的那个集群连接中,使用flushdb,清除数据库,在执行创建.

4. 如果使用flushdb,出现(error) READONLY You can’t write against a read only slave.

原因是因为当前的数据不是存储在这个节点上,你需要get key获得该数据,并跳往那个存储数据的真正节点,再执行flushdb命令.

5. 如果出现访问数据时,出现(error) MOVED 12539 127.0.0.1:7006

原因是没办法到达存储数据的那个节点,因为你客户端连接时没有指明 -c 参数,所以连接的是单个节点.

要执行这个 redis1/redis-cli -h 127.0.0.1 -p 7002 -c 命令.

6. 启动项目redis应用报错JedisClusterException: CLUSTERDOWN Hash slot not served解决

要确保使用 ./redis-trib.rb create --replicas 1 命令创建redis集群成功.

 

THE END

发表回复