Redis(十五):进阶篇 - 集群

本文详细介绍了Redis集群的配置步骤,包括启动集群、配置集群、节点加入、插槽分配以及故障恢复机制。通过redis-trib.rb工具创建3主3从的集群,并展示了使用CLUSTERMEET命令使节点加入集群。此外,还讨论了键与插槽的对应关系,以及如何通过CLUSTERADDSLOTS分配插槽。在故障恢复方面,解释了当主数据库下线时,从数据库如何进行故障转移以保持集群的正常运行。
摘要由CSDN通过智能技术生成

集群(Cluster)

  集群的特点在于拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。另外集群支持几乎所有的单机实例支持的命令,对于涉及多键的命令(如MGET),如果每个键都位于同一节点中,则可以正常支持,否则会提示错误。除此之外集群还有一个限制是只能使用默认的0号数据库,如果执行 SELECT 切换数据库则会提示错误。

1、配置集群

  使用集群,只需要将每个数据库节点的 cluster-enabled 配置选项打开即可。每个集群中至少需要3个主数据库才能正常运行。
  演示使用3主3从的集群系统。首先建立启动6个Redis实例,通过./install_server.sh脚本创建6个Redis实例,参数 《 Redis(二):初始篇-Linux安装Redis 5.2 将Redis安装成服务》,随系统启动 需要注意的是配置文件中应该打开 cluster-enabled,以及修改nodes文件名称一个示例配置如下为:

port 6380
cluster-enabled yes
cluster-config-file node_6380.conf

其中 port 参数修改成实际的端口即可。这里假设6个实例的端口分别是6380、6381、6382、6383、6384和6385。集群会将当前节点记录的集群状态持久化地存储在指定文件中,这个文件默认为当前工作目录下的 nodes.conf。每个节点对应的文件必须不同,否则会造成启动失败,所以启动节点时要注意最后为每个节点使用不同的工作目录,或者通过cluster-config-file选项修改持久化文件的名称。
  启动命令如下:

[root@VM-0-17-centos ~]# redis-server /etc/redis/6380.conf

启动后的效果如图:
在这里插入图片描述

  启动完成后,使用 redis-cli 连接任意一个节点使用 INFO 命令判断集群是否正常启用了

[root@VM-0-17-centos ~]# redis-cli -p 6380
127.0.0.1:6380> info cluster
# Cluster
cluster_enabled:1

其中 cluster_enabled 为1表示集群正常启用了。现在每个节点都是完全独立的,要将它们加入同一个集群里还需要几个步骤。
  Redis源代码中提供了一个辅助工具 redis-trib.rb 可以非常方便地完成这一任务。因为 redis-trib.rb 使用Ruby语言编写的,所以运行前需要在服务器上安装Ruby程序,执行以下命令自动安装:

[root@VM-0-17-centos redis]# sudo yum install ruby -y
Loaded plugins: fastestmirror, langpacks
Determining fastest mirrors
...
[root@VM-0-17-centos redis]# ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

  使用redis-trib-rb来初始化集群,只需要执行:

/root/redis-6.2.4/src/redis-trib.rb create --replicas 1 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

新版本提示使用以下命令
./redis-cli --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

  其中create参数表示要初始化集群, --cluster-replicas 1 表示每个主数据库拥有的从数据库个数为1,所以整个集群共有3个主数据库以及3个从数据库。
  执行完 redis-trib-rb 会输出以下内容:

[root@VM-0-17-centos src]# ./redis-cli --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
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6385 to 127.0.0.1:6381
Adding replica 127.0.0.1:6383 to 127.0.0.1:6382
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 9cf4e3c6639e71d7219581677457a2a53062de34 127.0.0.1:6380
   slots:[0-5460] (5461 slots) master
M: 4f714b0ebd163486bbd8138d22d9fd3d1bd26649 127.0.0.1:6381
   slots:[5461-10922],[12539] (5462 slots) master
M: 6f057a3a80b7efa40c1ca998c5d5909821d5f8af 127.0.0.1:6382
   slots:[10923-16383] (5461 slots) master
S: 619878748ab4a7898c45b9fcdc4f6ccecf7507a1 127.0.0.1:6383
   replicates 6f057a3a80b7efa40c1ca998c5d5909821d5f8af
S: 7deba04c968dcddda98847d4f6a36641e74775da 127.0.0.1:6384
   replicates 9cf4e3c6639e71d7219581677457a2a53062de34
S: cceff3c7921abb12dbe6b2fe4e11b8004dbc2b78 127.0.0.1:6385
   replicates 4f714b0ebd163486bbd8138d22d9fd3d1bd26649
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
...

  内容包括集群具体的分配方案,如果觉得没问题则输入yes来开始创建。下面根据上面的输出详细介绍集群创建的过程。

  首先 redis-trib-rb 会以客户端的形式尝试连接所有的节点,并发送 PING 命令以确定节点能够正常服务。如果有任何节点无法连接,则创建失败。同时发送 INFO 命令获取每个节点的运行ID以及是否开启了集群功能(即 cluster-enabled为1)。
  准备就绪后集群会向每个节点发送 CLUSTER MEET命令,格式为 CLUSTER MEET ip port,这个命令用来告诉当前节点指定ip和port上运行节点也是集群的一部分,从而使得6个节点最终可以归入一个集群。
  然后 redis-trib-rb 会分配主从数据库节点,分配的原则是尽量保证每个主数据库运行在不同的IP地址上,同时每个从数据库和主数据库均不运行在同一IP地址上,以保证系统的容灾能力。分配结果如下:

Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6385 to 127.0.0.1:6381
Adding replica 127.0.0.1:6383 to 127.0.0.1:6382

其中主数据库是6380、6381和6382端口上的节点(以下使用端口号来代指节点),6384是6380的从数据库,6385是6381的从数据库,6383是6382的从数据库。
  分配完成后,会为每个主数据库分配插槽,分配插槽的过程其实就是分配哪些键归哪些节点负责。之后对每个要成为子数据库的节点发送 CLUSTER REPLICATE 主数据库的运行ID 来将当前节点转换成从数据库并复制指定运行ID的节点(主数据库)。
  此时整个集群的过程创建完成。

使用 cluster nodes 获得集群中所有节点信息:

127.0.0.1:6381> cluster nodes
9cf4e3c6639e71d7219581677457a2a53062de34 127.0.0.1:6380@16380 master - 0 1625382979738 1 connected 0-5460
7deba04c968dcddda98847d4f6a36641e74775da 127.0.0.1:6384@16384 slave- 0 1625382981000 5 connected
4f714b0ebd163486bbd8138d22d9fd3d1bd26649 127.0.0.1:6381@16381 myself,master - 0 1625382979000 2 connected 5461-10922
619878748ab4a7898c45b9fcdc4f6ccecf7507a1 127.0.0.1:6383@16383 slave- 0 1625382980792 4 connected
6f057a3a80b7efa40c1ca998c5d5909821d5f8af 127.0.0.1:6382@16382 master - 0 1625382981835 3 connected 10923-16383
cceff3c7921abb12dbe6b2fe4e11b8004dbc2b78 127.0.0.1:6385@16385 slave- 0 1625382979000 6 connected

从上面的输出中可以看到所有节点的运行ID、地址和端口、角色、状态以及负责的插槽信息。

2、节点的增加

  前面介绍过 redis-trib-rb 是使用 CLUSTER MEET 命令来使每个节点认识集群中的其他节点的,可想而知如果要想向集群中加入新的节点,也需要使用 CLUSTER MEET 命令实现。加入新节点非常简单,只需要向新节点(以下记作A)发送如下命令即可:

CLUSTER MEET ip port

ip和port是集群中任意一个节点的地址和端口号,A接收到客户端发送的命令后,会与该地址和端口号的节点B进行握手,使B将A认作当前集群中的一员。当B与A握手成功后,B会使用Gossip协议将节点A的信息通知给集群中的每一节点。通过这一方式,即使集群中有多个节点,也只需要选择MEET其中任意一个节点,即可使新节点最终加入整个集群中。

3、插槽的分配

  新的节点加入集群后有两种选择,要么使用 CLUSTER REPLICATE命令复制每个主数据库来以从数据库的形式运行,要么向集群申请分配插槽(slot)来以主数据库的形式运行。
  在一个集群中,所有的键会被分配给16384个插槽,而每个主数据库会负责处理其中的一部分插槽。创建集群时主数据库分配的插槽信息如下:

M: 9cf4e3c6639e71d7219581677457a2a53062de34 127.0.0.1:6380
   slots:[0-5460] (5461 slots) master
M: 4f714b0ebd163486bbd8138d22d9fd3d1bd26649 127.0.0.1:6381
   slots:[5461-10922],[12539] (5462 slots) master
M: 6f057a3a80b7efa40c1ca998c5d5909821d5f8af 127.0.0.1:6382
   slots:[10923-16383] (5461 slots) master

  上面的每一行表示一个主数据库的信息,其中可以看到6380负责处理0 ~ 5460这5461个插槽,6381负责5461 ~ 10922 这5462个插槽,6382负责10923 ~ 16383这5461个插槽。虽然redis-trib.rb初始化集群时分配给每个节点的插槽都是连续的,但是实际上Redis并没有此限制,可以将任意的几个插槽分配给任意的节点负责。

  键与插槽的对应关系。Redis将每个键的键名的有效部分使用CRC16算法计算出散列值,然后取对16384的余数。这样使得每个键都可以分配到16384个插槽中,进而分配的指定的一个节点中处理。键名的有效部分是指:
  (1)如果键名包含 { 符号,且在 { 符号后面存在 } 符号,并且 { 和 } 之间有至少一个字符,则有效部分是指 { 和 } 之间的内容;
  (2)如果不满足上一条规则,那么整个键名为有效部分。

  例如,键hello.world的有效部分为“hello.world”,键{user102}:last.name的有效部分是“user102”。如果命令涉及多个键(如MGET),只有当所有键位于同一个节点时Redis才能正常支持。利用键的分配规则,可以将所有相关的键的有效部分设置成同样的值使得相关键都能分配到同一个节点以支持多键操作。比如,{user102}:first.name和{user102}:last.name会被分配到同一节点,所以可以使用 MGET {user102}:first.name {user102}:last.name来同时获取两个键的值。

  如何将插槽分配给指定节点。插槽的分配分为如下几种情况。
  (1)插槽之前没有被分配过,现在想分配给指定节点;
  (2)插槽之前被分配过,现在想移动到指定节点;

  其中第一种情况使用 CLUSTER ADDSLOTS slot1 命令来实现,redis-trib-rb 也是通过该命令在创建集群时为新节点分配插槽的。CLUSTER ADDSLOTS命令的用法为:

CLUSTER ADDSLOTS slot1 [slot2] ...  [slotN]

如想将100和101两个插槽分配给某个节点,只需要在该节点执行:CLUSTER ADDSLOTS 100 101 即可。如果指定插槽已经分配过了,则会提示:

127.0.0.1:6381> cLUSTER ADDSLOTS 100 101
(error) ERR Slot 100 is already busy

可以通过CLUSTER SLOTS来查看插槽的分配情况:

127.0.0.1:6381> CLUSTER SLOTS
1) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 6380
   4) 1) "127.0.0.1"
      2) (integer) 6384
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 6381
   4) 1) "127.0.0.1"
      2) (integer) 6385
3) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 6382
   4) 1) "127.0.0.1"
      2) (integer) 6383

其中返回结果的格式很容易理解,一共3条记录,每条记录的前两个值表示插槽的开始号码和结束号码,后面的值则为负责该插槽的节点,包括主数据库和所有的从数据库,主数据库始终在第一。

4、获取与插槽对应的节点

  如何获取某一个键由哪个节点负责呢?
  实际上,当客户端向集群中的任意一个节点发送命令后,该节点会判断相应的键是否在当前节点中,如果键在该节点中,则会像单机实例一样正常处理该命令;如果键不在该节点中,就会返回一个 MOVE 重定向请求,告诉客户端这个键目前由哪个节点负责,然后客户端再将同样的请求向目标节点重新发送一次以获得结果。

  以上面为例,键 foo实际应该由6382节点负责,如果尝试在6381节点执行与键foo相关的命令,就会有如下输出:

127.0.0.1:6381> set foo key
(error) MOVED 12182 127.0.0.1:6382

返回的是一个MOVE重定向请求,12182 表示 foo所属的插槽号,127.0.0.1:6382则是负责该插槽的节点地址和端口,客户端收到重定向请求后,应该将命令重新向6382节点发送一次:

[root@VM-0-17-centos ~]# redis-cli -p 6382
127.0.0.1:6382> set foo key
OK

  Redis命令行客户端提供了集群模式来支持自动重定向,使用 -c 参数来启用:

[root@VM-0-17-centos ~]# redis-cli -c -p 6381
127.0.0.1:6381> set foo 111
-> Redirected to slot [12182] located at 127.0.0.1:6382
OK

  可见加入了 -c 参数后,如果当前节点并不负责要处理的键,Redis命令行客户端会进行自动命令重定向。而这一过正是每个支持集群的客户端应该实现的。

  然而相比单机实例,集群的命令重定向也增加了命令的请求次数,原先只需要执行一次的命令现在有可能需要依次发向两个节点,算上往返时延,可以说请求重定向对性能有一定影响。
  为了解决这一问题,当发现新的重定向请求时,客户端应该在重新向正确节点发送命令的同时,缓存插槽的路由信息,即记录下当前插槽是由哪个节点负责的。这样每次发起命令时,客户端首先计算相关键是属于哪个插槽的,然后根据缓存的路由判断插槽由哪个节点负责。考虑到插槽总数相对较少(16384),缓存所有插槽的路由信息后,每次命令将均发向正确的节点,从而达到和单机实例同样的性能。

5、故障恢复

  在一个集群中,每个节点都会定期向其他节点发送 PING 命令,并通过有没有收到回复来判断目标节点是否已经下线了。具体来说,集群中的每个节点每隔1秒就会随机选择5个节点,然后选择其中最久没有响应的节点发送 PING 命令。
  如果一定时间内目标节点没有响应回复,则发起 PING 命令的节点会认为目标节点疑似下线。疑似下线可以与哨兵的主观下线类比,两者都表示某一节点从自身的角度认为目标节点是下线的状态。与哨兵的模式类似,如果要使在整个集群中的所有节点都认为某一节点已经下线,需要一定数量的节点都认为该节点疑似下线才可以,这一过程具体为:
  (1)一旦节点A认为B是疑似下线状态,就会在集群中传播该消息,所有其他节点时候到消息后都会记录下这一信息;
  (2)当集群中的某一节点C收集到半数以上的节点认为B是疑似下线的状态时,就会将B标记为下线,并且向集群中的其他节点传播该消息,从而使得B在整个集群中下线。

  在集群中,当一个主数据库下线时,就会出现一部分插槽无法写入的问题。这时如果该主数据库拥有至少一个从数据库,集群就进行故障恢复操作将其中一个从数据库转变成主数据库来保证集群的完整。选择哪个从数据来作为主数据库的过程与在哨兵中选择领头哨兵的过程一样,都是基于Raft算法,过程如下:
  (1)发现其复制的主数据库下线的从数据库(线面称作A)向集群中的每个节点发送请求,要求对方选自己成为主数据库;
  (2)如果收到请求的节点没有选过其他人,则会同意将A设置成主数据库;
  (3)如果A发现有超过集群中节点总数一半的节点同意选自己成为主数据库,则A成功成为主数据库;
  (4)当有多个从数据库节点同时参选主数据库,则会出现没有任何接地那当选的可能。此时每个参选节点将等待一个随机事件重新发起参选请求,进行下一轮选举,直到选举成功。

  当某个从数据库当选为主数据库后,会通过命令 SLAVEOF ON ONE 将自己转换成主数据库,并将旧的主数据库的插槽转换给自己负责。
  如果一个至少负责一个插槽的主数据库下线且没有相应的从数据库可以进行故障恢复,则整个集群默认会进入下线状态无法继续工作。如果想在这种情况下使集群仍能正常工作,可以修改配置 cluster-require-full-coverage为no(默认为yes):

cluster-require-full-coverage no

author:su1573
鄙人记录生活点滴,学习并分享,请多指教!!!
如需交流,请联系 sph1573@163.com,鄙人看到会及时回复

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ssy03092919

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值