大点干!早点散----------深入剖析Redis集群原理与实验

时间:2022-07-24
本文章向大家介绍大点干!早点散----------深入剖析Redis集群原理与实验,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

文章目录

一、 Redis 集群的实现

分散单台服务器的访问压力,实现负载均衡 分散单台服务器的存储压力,实现可扩展性 降低单台服务器宕机带来的业务灾难

1、数据分片

(1)客户端实现数据分片

即客户端自己计算数据的key应该在哪个机器上存储和查找。

此方法的好处是降低了服务器集群的复杂度,客户端实现数据分片时,服务器是独立的,服务器之前没有任何关联。多数redis客户端库实现了此功能,也叫sharding。

这种方式的缺点是客户端需要实时知道当前集群节点的联系信息,同时,当添加一个新的节点时,客户端要支持动态sharding.,多数客户端实现不支持此功能,需要重启redis。另一个弊端是redis的HA需要额外考虑。

(2)服务器实现数据分片

其理论是,客户端随意与集群中的任何节点通信,服务器端负责计算某个key在哪个机器上,当客户端访问某台机器时,服务器计算对应的key应该存储在哪个机器,然后把结果返回给客户端,客户端再去对应的节点操作key,是一个重定向的过程,某个master节点挂了后,其slave会自动接管。

服务器端实现集群需要客户端语言实现服务器集群的协议,目前java,php,ruby语言多数有redis-cluster客户端实现版本。

Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将所有的key映射到16384个Slot中,集群中的每个Redis Instance负责其中的一部分的Slot的读写。集群客户端连接集群中任一Redis Instance即可发送命令,当Redis Instance收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求重新发往这个地址,对外部透明。一个Key到底属于哪个Slot由crc16(key) % 16384 决定。

关于负载均衡,集群的Redis Instance之间可以迁移数据,以Slot为单位,但不是自动的,需要外部命令触发。

关于集群成员管理,集群的节点(Redis Instance)和节点之间两两定期交换集群内节点信息并且更新,从发送节点的角度看,这些信息包括:集群内有哪些节点,IP和PORT是什么,节点名字是什么,节点的状态(比如OK,PFAIL,FAIL,后面详述)是什么,包括节点角色(master 或者 slave)等。

关于可用性,集群由N组主从Redis Instance组成。主可以没有从,但是没有从 意味着主宕机后主负责的Slot读写服务不可用。

一个主可以有多个从,主宕机时,某个从会被提升为主,具体哪个从被提升为主,协议类似于Raft,参见这里。如何检测主宕机?Redis Cluster采用quorum+心跳的机制。从节点的角度看,节点会定期给其他所有的节点发送Ping,cluster-node-timeout(可配置,秒级)时间内没有收到对方的回复,则单方面认为对端节点宕机,将该节点标为PFAIL状态。通过节点之间交换信息收集到quorum个节点都认为这个节点为PFAIL,则将该节点标记为FAIL,并且将其发送给其他所有节点,其他所有节点收到后立即认为该节点宕机。从这里可以看出,主宕机后,至少cluster-node-timeout时间内该主所负责的Slot的读写服务不可用。

(3)通过代理服务器实现数据分片

此方式是借助一个代理服务器实现数据分片,客户端直接与proxy联系,proxy计算集群节点信息,并把请求发送到对应的集群节点。降低了客户端的复杂度,需要proxy收集集群节点信息。Twemproxy是twitter开源的,实现这一功能的proxy。这个实现方式在客户端和服务器之间加了一个proxy,但这是在redis 3.0稳定版本出来之前官方推荐的方式。结合redis-sentinel的HA方案,是个不错的组合。

(4)Redis集群数据分片

Redis Cluster不使用一致的哈希,而是使用不同形式的分片,其中每个键从概念上讲都是我们称为哈希槽的一部分。

Redis集群中有16384个哈希槽,要计算给定密钥的哈希槽,我们只需对密钥的CRC16取模16384。

Redis群集中的每个节点都负责哈希槽的子集,因此,例如,您可能有一个包含3个节点的群集,其中:

节点A包含从0到5500的哈希槽。 节点B包含从5501到11000的哈希槽。 节点C包含从11001到16383的哈希槽。 这样可以轻松添加和删除集群中的节点。例如,如果要添加新节点D,则需要将一些哈希槽从节点A,B,C移到D。类似地,如果要从群集中删除节点A,则可以仅移动A提供的哈希槽到B和C。当节点A为空时,我可以将其从群集中完全删除。

因为将哈希槽从一个节点移动到另一个节点不需要停止操作,所以添加和删除节点或更改节点持有的哈希槽的百分比不需要任何停机时间。

2、Redis群集TCP端口

每个Redis群集节点都需要打开两个TCP连接。用于服务客户端的常规Redis TCP端口,例如6379,加上通过将10000添加到数据端口而获得的端口,因此在示例中为16379。

第二个高端口用于群集总线,即使用二进制协议的节点到节点通信通道。节点将群集总线用于故障检测,配置更新,故障转移授权等。客户端永远不要尝试与群集总线端口进行通信,而应该始终与普通的Redis命令端口进行通信,但是请确保您同时打开防火墙中的两个端口,否则Redis群集节点将无法进行通信。

命令端口和集群总线端口偏移是固定的,并且始终为10000。

请注意,对于每个节点,要使Redis群集正常工作,您需要:

普通客户端通信端口(通常为6379)用于与客户端通信,以向需要访问群集的所有客户端以及所有其他群集节点(使用客户端端口进行密钥迁移)开放。 群集总线端口(客户端端口+ 10000)必须可以从所有其他群集节点访问。 如果您没有同时打开两个TCP端口,则群集将无法正常工作。

3、Redis Cluster的高可用

(1)Redis Cluster主从模型

为了在主节点子集出现故障或无法与大多数节点通信时保持可用,Redis Cluster使用主从模型,其中每个哈希槽具有从1(主节点本身)到N个副本(N个) -1个其他从属节点)。

在具有节点A,B,C的示例集群中,如果节点B失败,则集群将无法继续,因为我们不再有办法为5501-11000范围内的哈希槽提供服务。

但是,在创建集群(或稍后)时,我们向每个主节点添加一个从属节点,以便最终集群由作为主节点的A,B,C和作为从属节点的A1,B1,C1组成,如果节点B发生故障,系统将能够继续。

节点B1复制B,并且B失败,群集将把节点B1提升为新的主节点,并将继续正常运行。

但是请注意,如果节点B和B1同时失败,则Redis Cluster无法继续运行。

(2)Redis群集一致性保证

Redis Cluster无法保证强一致性。实际上,这意味着在某些情况下,Redis Cluster可能会丢失系统已确认给客户端的写入。

Redis Cluster可能丢失写入的第一个原因是因为它使用异步复制。这意味着在写入期间会发生以下情况:

您的客户写信给主B。 主B向您的客户答复“确定”。 主机B将写操作传播到其从机B1,B2和B3。 如您所见,B在回复客户端之前不会等待B1,B2,B3的确认,因为这会对Redis造成极高的延迟,因此,如果您的客户端写了一些东西,B会确认写,但是在崩溃之前崩溃由于能够将写操作发送到其从属设备,因此一个从属设备(未接收到写操作)可以提升为主设备,从而永远丢失写操作。

这与配置为每秒将数据刷新到磁盘的大多数数据库所发生的情况非常相似,因此由于过去在不涉及分布式系统的传统数据库系统中的经验,您已经可以对此进行推理。同样,您可以通过强制数据库在答复客户端之前刷新磁盘上的数据来提高一致性,但这通常会导致性能过低。在Redis Cluster的情况下,这相当于同步复制。 基本上,需要在性能和一致性之间进行权衡。

故障转移期间,会丢失少量数据

二、Redis集群配置参数

我们将创建一个示例集群部署。在继续之前,让我们介绍Redis Cluster在redis.conf文件中引入的配置参数。当您继续阅读时,有些会很明显,有些会更清晰。

cluster-enabled<yes/no>:如果是,则在特定的Redis实例中启用Redis Cluster支持。否则,该实例将像往常一样作为独立实例启动。 cluster-config-file:请注意,尽管有此选项的名称,但它不是用户可编辑的配置文件,而是Redis Cluster节点每次发生更改时都会自动持久保存集群配置的文件(状态,基本上是状态),为了能够在启动时重新阅读它。该文件列出了诸如集群中其他节点之类的东西,它们的状态,持久变量等等。通常,由于收到某些消息,此文件将被重写并刷新到磁盘上。 cluster-node-timeout:Redis群集节点不可用的最长时间(不将其视为失败)。如果主节点无法访问的时间超过指定的时间量,则其主节点将对其进行故障转移。此参数控制Redis Cluster中的其他重要内容。值得注意的是,在指定的时间内无法到达大多数主节点的每个节点都将停止接受查询。 cluster-slave-validity-factor:如果设置为零,则从服务器将始终尝试对主服务器进行故障转移,而不管主服务器和从服务器之间的链接保持断开状态的时间长短。如果该值为正,则将最大断开时间计算为节点超时值乘以此选项提供的因子,如果节点是从节点,则如果断开主链接的时间超过指定的时间,它将不会尝试启动故障转移。例如,如果节点超时设置为5秒,而有效性因子设置为10,则从服务器与主服务器断开连接超过50秒将不会尝试对其主服务器进行故障转移。请注意,如果没有从属能够对其进行故障转移,则任何不为零的值都可能导致Redis Cluster在主控发生故障后不可用。在这种情况下,只有当原始主服务器重新加入群集后,群集才会恢复可用。 cluster-migration-barrier:主机将保持连接的最小数量的从机,以便另一个从机迁移到不再受任何从机覆盖的主机。有关更多信息,请参见本教程中有关副本迁移的相应部分。 cluster-require-full-coverage<yes/no>:如果设置为yes,默认情况下,如果某个节点没有覆盖一定比例的密钥空间,集群将停止接受写入。如果该选项设置为no,即使仅可以处理有关密钥子集的请求,群集仍将提供查询。 cluster-allow-reads-when-down<yes/no>:如果将其设置为no(默认情况下为默认值),则当Redis群集被标记为失败时,或者当节点无法到达时,Redis群集中的节点将停止为所有流量提供服务达不到法定人数或完全覆盖。这样可以防止从不知道群集更改的节点读取可能不一致的数据。可以将此选项设置为yes,以允许在失败状态期间从节点进行读取,这对于希望优先考虑读取可用性但仍希望防止写入不一致的应用程序很有用。当仅使用一个或两个分片的Redis Cluster时,也可以使用它,因为它允许节点在主服务器发生故障但无法进行自动故障转移时继续为写入提供服务。

三、redis集群搭建

1、实验环境

VMware中六台centos7.6虚拟机,三台为master服务器,三台为slave,地址规划如下: Master1:192.168.110.132 slave1:192.168.110.136 Master2:192.168.110.133 slave2:192.168.110.137 Master3:192.168.110.134 slave3:192.168.110.135

2、拓扑图大致如下

3、实验过程

(1)手工编译安装redis

六台都要装

[root@redis ~]# yum install gcc gcc-c++ make -y

[root@redis redis]# tar zxvf redis-5.0.7.tar.gz -C /opt	'//redis源码包可以直接到官网下载'
[root@redis redis]# cd /opt/redis-5.0.7/
[root@redis redis-5.0.7]# make	         '//直接进行make'
[root@redis redis-5.0.7]#  make PREFIX=/usr/local/redis/ install	'//指定redis目录并安装'
[root@redis redis-5.0.7]# cd /usr/local/redis/
[root@redis redis]# ls
bin
[root@redis redis]# cd bin/
[root@redis bin]# ls
redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server	'//redis-cli是连接终端'

[root@redis bin]# cd /opt/redis-5.0.7/utils/	'//回到redis源码包解压目录'
[root@redis utils]# ./install_server.sh 	'//执行脚本进行配置'
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379] 	'//选择redis默认接口,直接回车'
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] 	'//选择redis默认配置文件名称,直接回车'
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log] 	'//选择默认redis日志文件名称,直接回车'
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] 	'//选择默认接口的默认数据文件,直接回车'
Selected default - /var/lib/redis/6379
Please select the redis executable path [] /usr/local/redis/bin/redis-server	'//选择redis可执行文件路径,需要手动输入此路径:/usr/local/redis/bin/redis-server'
Selected config:	'//选择的配置清单展示'
Port           : 6379
Config file    : /etc/redis/6379.conf
Log file       : /var/log/redis_6379.log
Data dir       : /var/lib/redis/6379
Executable     : /usr/local/redis/bin/redis-server
Cli Executable : /usr/local/redis/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.	'//直接回车完成配置'
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!

[root@master1 utils]# vim /etc/redis/6379.conf 	'//修改主配置文件'
#bind 127.0.0.1	'//注释第70行的监听127地址,表示监听所有地址'
protected-mode no	'//去掉第89行注释关闭安全保护'
port 6379	'//去掉第93行注释,开启端口6379'
daemonize yes	'//去掉第137行注释,以独立进程启动'
cluster-enabled yes	'//去掉第833行注释,开启群集功能'
cluster-config-file nodes-6379.conf	'//去掉第841行注释,群集名称文件设置'
cluster-node-timeout 15000	'//去掉第847行注释,群集超时时间设置'
appendonly yes	'//去掉第700行注释,开启aof持久化'
[root@master1 utils]# cd /var/lib/redis/6379/
[root@master1 6379]# /etc/init.d/redis_6379 restart	'//重启redis服务'
[root@master1 6379]# ls
appendonly.aof  dump.rdb  nodes-6379.conf	'//生成了三个文件,appendonly.aof是AOF持久化文件,dump.rdb是RDB快照文件,nodes-6379.conf是节点首次启动生成的配置文件'
[root@redis utils]# ln -s /usr/local/redis/bin/* /usr/local/bin	'//将redis命令创建软连接,便于系统识别'
[root@redis utils]# netstat -ntap |grep 6379
tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN      26085/redis-server  

[root@redis utils]# /etc/init.d/redis_6379 stop	'//先关闭redis服务'
Stopping ...
Redis stopped
[root@redis utils]# /etc/init.d/redis_6379 start	'//开启redis服务'
Starting Redis server...
[root@redis utils]# netstat -ntap |grep 6379	'//再次检查redis开启情况'

(2)在一台master服务器上安装rvm,RUBY控制集群软件

[root@localhost utils]# gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
gpg: 已创建目录‘/root/.gnupg’
gpg: 新的配置文件‘/root/.gnupg/gpg.conf’已建立
gpg: 警告:在‘/root/.gnupg/gpg.conf’里的选项于此次运行期间未被使用
gpg: 钥匙环‘/root/.gnupg/secring.gpg’已建立
gpg: 钥匙环‘/root/.gnupg/pubring.gpg’已建立
gpg: 下载密钥‘D39DC0E3’,从 hkp 服务器 keys.gnupg.net
gpg: /root/.gnupg/trustdb.gpg:建立了信任度数据库
gpg: 密钥 D39DC0E3:公钥“Michal Papis (RVM signing) <mpapis@gmail.com>”已导入
gpg: 没有找到任何绝对信任的密钥
gpg: 合计被处理的数量:1
gpg:           已导入:1  (RSA: 1)

导入key文件后,可以使用下面这条命令下载rvm,不过需要FQ,这里就直接使用FQ得到的脚本进行安装

[root@localhost utils]# chmod +x rvm-installer.sh    ##赋予执行权限
[root@localhost utils]# ./rvm-installer.sh           ##执行脚本下载
...
省略部分信息
Thanks for installing RVM ?
Please consider donating to our open collective to help us maintain RVM.

  Donate: https://opencollective.com/rvm/donate
[root@localhost redis-5.0.7]# cd /etc/profile.d/
[root@localhost profile.d]# ls
[root@localhost profile.d]# rvm install 2.4.10    ##耐心等待下载完成,下载时间完全与网速有关
[root@localhost profile.d]# rvm use 2.4.10     ##使用ruby
Using /usr/local/rvm/gems/ruby-2.4.10
[root@localhost profile.d]# ruby -v    ##查看版本
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

再次安装redis

[root@localhost profile.d]# gem install redis
Fetching redis-4.2.2.gem
Successfully installed redis-4.2.2
Parsing documentation for redis-4.2.2
Installing ri documentation for redis-4.2.2
Done installing documentation for redis after 3 seconds
1 gem installed

(3)下载完毕ruby后创建集群

[root@localhost profile.d]# redis-cli --cluster create 192.168.110.132:6379 192.168.110.133:6379 192.168.110.134:6379 192.168.110.135:6379 192.168.110.136:6379 192.168.110.137:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460    '哈希槽分配'
Master[1] -> Slots 5461 - 10922     
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.110.136:6379 to 192.168.110.132:6379   '主从对应'
Adding replica 192.168.110.137:6379 to 192.168.110.133:6379
Adding replica 192.168.110.135:6379 to 192.168.110.134:6379
M: edfd936cd7f96f93b347f5597c5dc779f9b6f2c1 192.168.110.132:6379
   slots:[0-5460] (5461 slots) master
M: 6be294a70787981929ac3969d952828b9eaf4acd 192.168.110.133:6379
   slots:[5461-10922] (5462 slots) master
M: 79a3935cee2ba2172f481af2b70ffcc038cb27c5 192.168.110.134:6379
   slots:[10923-16383] (5461 slots) master
S: d8831e98a2b9dd44e84ee97703939db146088a6d 192.168.110.135:6379
   replicates 79a3935cee2ba2172f481af2b70ffcc038cb27c5
S: 081f50022d933cb4a8085b74b860ea7a0d642045 192.168.110.136:6379
   replicates edfd936cd7f96f93b347f5597c5dc779f9b6f2c1
S: 8b09a54a86cd8c92e75c433f5b8448d783fabb76 192.168.110.137:6379
   replicates 6be294a70787981929ac3969d952828b9eaf4acd
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.....
>>> Performing Cluster Check (using node 192.168.110.132:6379)
M: edfd936cd7f96f93b347f5597c5dc779f9b6f2c1 192.168.110.132:6379    '各节点的运行id'
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: d8831e98a2b9dd44e84ee97703939db146088a6d 192.168.110.135:6379
   slots: (0 slots) slave
   replicates 79a3935cee2ba2172f481af2b70ffcc038cb27c5
S: 081f50022d933cb4a8085b74b860ea7a0d642045 192.168.110.136:6379
   slots: (0 slots) slave
   replicates edfd936cd7f96f93b347f5597c5dc779f9b6f2c1
S: 8b09a54a86cd8c92e75c433f5b8448d783fabb76 192.168.110.137:6379
   slots: (0 slots) slave
   replicates 6be294a70787981929ac3969d952828b9eaf4acd
M: 6be294a70787981929ac3969d952828b9eaf4acd 192.168.110.133:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 79a3935cee2ba2172f481af2b70ffcc038cb27c5 192.168.110.134:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

上面的方法创建群集时的主从关系是前三个是master 后面的slave随机匹配前面三个。

(4)测试集群数据共享

在192.168.110.132上创建一个key值 然后去集群中的任意一台进行查看 发现全部可以查看

192.168.110.137:6379> set dog gousheng
-> Redirected to slot [254] located at 192.168.110.132:6379
OK

[root@localhost ~]# redis-cli -c -h 192.168.110.134
192.168.110.134:6379> get dog
-> Redirected to slot [254] located at 192.168.110.132:6379
"gousheng"


[root@localhost ~]# tail -f /var/log/redis_6379.log '查看集群日志'
  37380:S 10 Sep 2020 12:08:06.636 * 1 changes in 900 seconds. Saving...
37380:S 10 Sep 2020 12:08:06.637 * Background saving started by pid 45257
45257:C 10 Sep 2020 12:08:06.642 * DB saved on disk     '同步成功'
45257:C 10 Sep 2020 12:08:06.643 * RDB: 0 MB of memory used by copy-on-write
37380:S 10 Sep 2020 12:08:06.738 * Background saving terminated with success

也就是说数据在132的机器master上存着 只不过他可以转过去帮你看

(5)测试将刚刚存数据master down掉后

[root@localhost ~]# tail -f /var/log/redis_6379.log '查看集群日志'
'fail字段,slave检测出masterdown掉了,进行替补  '37380:S 10 Sep 2020 12:14:16.633 * FAIL message received from     d8831e98a2b9dd44e84ee97703939db146088a6d about edfd936cd7f96f93b347f5597c5dc779f9b6f2c1
37380:S 10 Sep 2020 12:14:16.633 # Cluster state changed: fail    'master已经down掉'
37380:S 10 Sep 2020 12:14:16.675 # Start of election delayed for 771 milliseconds (rank #0, offset 9103).
37380:S 10 Sep 2020 12:14:17.482 # Starting a failover election for epoch 9.
37380:S 10 Sep 2020 12:14:17.489 # Failover election won: I'm the new master.
37380:S 10 Sep 2020 12:14:17.489 # configEpoch set to 9 after successful failover
37380:M 10 Sep 2020 12:14:17.489 # Setting secondary replication ID to d7458497dc9a8e73409003484d6887668978dce9, valid up to offset: 9104. New replication ID is 0189aff59b6429b793078ee6a46841479b945588
37380:M 10 Sep 2020 12:14:17.489 # Connection with master lost.   'slave已经联系不到master了'
37380:M 10 Sep 2020 12:14:17.489 * Caching the disconnected master state.  '准备顶替master'
37380:M 10 Sep 2020 12:14:17.490 * Discarding previously cached master state.
37380:M 10 Sep 2020 12:14:17.490 # Cluster state changed: ok   'slave 顶替master成功  集群恢复正常'

(6)集群无法使用总结

对应的master和slave同时挂掉后集群就无法运行了 三台master同时挂掉集群无法运行

主要还是跟集群的哈希槽的特性导致的,

4、查看集群的情况

[root@localhost ~]# vim /var/lib/redis/6379/nodes-6379.conf
79a3935cee2ba2172f481af2b70ffcc038cb27c5 192.168.110.134:6379@16379 myself,slave d8831e98a2b9dd44e84ee97703939db146088a6d 0 1599711256000 3 connected
081f50022d933cb4a8085b74b860ea7a0d642045 192.168.110.136:6379@16379 master - 0 1599711255555 9 connected 0-5460
edfd936cd7f96f93b347f5597c5dc779f9b6f2c1 192.168.110.132:6379@16379 master,fail - 1599711242438 1599711238000 1 connected
8b09a54a86cd8c92e75c433f5b8448d783fabb76 192.168.110.137:6379@16379 master - 0 1599711257000 8 connected 5461-10922
d8831e98a2b9dd44e84ee97703939db146088a6d 192.168.110.135:6379@16379 master - 0 1599711256563 7 connected 10923-16383
6be294a70787981929ac3969d952828b9eaf4acd 192.168.110.133:6379@16379 master,fail - 1599706480591 1599706473000 2 connected
vars currentEpoch 9 lastVoteEpoch 0