最详细RedisCluster集群环境搭建与原理详解

上篇文章我们介绍了redis主从复制和哨兵模式搭建附上 链接地址
现在我们介绍下RedisCluster模式,我们知道redis 哨兵模式已经可以实现redis的主从自动故障切换,但是哨兵模式无法实现水平扩展,也就是我们只能有一个master节点提供服务,如果我们要缓存的数据非常多,那单机的master就无法满足我们的需求,redis给我们提供了一种更好的方式也就是Redis Cluster。Redis cluster实现了所有的哨兵模式的功能并且还可以支持水平扩展
先说明下环境,我们将完全实现生产环境的模拟,需要3台机器部署三主三从的架构共6个redis节点,我会在每天机器上配置一个主节点一个从节点,注意redis集群要求最少3个master(注意这里很多文章说必须是奇数个服务,这里说明不一定非要奇数,偶数也可以的),至于原因不用多说了,还是半数以上的选举策略
三个机器节点分布:
192.168.56.101 端口8001,8004
192.168.56.102 端口8002,8005
192.168.56.103 端口8003,8006
Note: redis5.0之后的版本都适用该文档,redis5.0之前是需要借助ruby脚本实现集权管理的,我使用的是6.0.9版本,

先上redis集群架构图
在这里插入图片描述

redis集群会把我们的三个master节点在逻辑上分成16384个hash slot,注意是逻辑上分的,每个节点会分配一部分,正常情况下是均分hash slot,
比如我上面第一个master分配0-5460,第二个分配5461-10922,第三个分配10923-16383,当我们执行一条命令时redis会把我们的key进行hash然后取模(0-16383),取模之后就可以定位到我们的数据要存储或者读取那个节点。
下面我们先把集群搭建起来在对redis集群做详细说明

集群搭建

  1. 首先我们在三台机器上都安装好redis,安装就不说了,基础的东西,我们在每台机器的redis目录下建一个redis-cluster目录,然后在redis-cluster目录下建当前机器要启动的两个redis服务对应的目录用来存放各自的配置文件,目录名使用端口号命名。直接上命令
#第一台机器
[root@localhost redis-6.0.9]# mkdir redis-cluster/{8001,8004}
#第二台机器
[root@localhost redis-6.0.9]# mkdir redis-cluster/{8002,8005}
#第三台机器
[root@localhost redis-6.0.9]# mkdir redis-cluster/{8003,8006}

创建好目录之后我们把redis.conf文件复制到每一个端口对应的目录下,这里就不贴命令了,然后逐个的修改redis.conf文件,注意每个配置文件的端口和目录不一样,要仔细修改。需要修改的配置项如下:

(1) #bind 127.0.0.1 # 注释掉这行,否则其他服务无法连接
(2) protected-mode no # 关闭保护模式,否则其他服务无法连接
(3) port 8001 # 端口,修改为各自的端口号
(4) daemonize yes # 开启后台运行
(5) pidfile /var/run/redis_8001.pid # 进程pid文件,使用端口号区分
(6) dir ./redis-cluster/8001/ # 指定数据文件存放位置,同样的放到redis_cluster目录的不同端口下
(7) appendonly yes # 开启aof持久化存储,可以查看我另一篇文章了解下redis持久化存储[链接](https://blog.csdn.net/humanhaunt/article/details/109512584)
(8) cluster-enabled yes # 关键配置,开启redis cluster
(9) cluster-config-file nodes-8001.conf # 集群节点信息文件,这里800x最好也和端口对应,下面会解释
(10) cluster-node-timeout 5000 #节点超时时间,下面会解释
(11) requirepass foobared # redis访问密码
(12) masterauth foobared # 设置集群节点间访问密码,和上面一致

上面的配置项都要修改为对应port的配置,可以先修改一个然后使用scp命令copy到其他机器的对应端口目录下
然后修改3,5,6,9项里的端口,可以用批量替换

:%s/源字符串/目的字符串/g

这里有点费劲,但要有耐心,所有的都配置好之后,我们就可以启动对应的实例了

  1. 启动每一个Redis实例
[root@localhost redis-6.0.9]# src/redis-server redis-cluster/8001/redis.conf
[root@localhost redis-6.0.9]# src/redis-server redis-cluster/8004/redis.conf

查看下我们的实例是否运行成功

[root@localhost redis-6.0.9]# ps -ef|grep redis
root      3672     1  0 16:28 ?        00:00:00 src/redis-server *:8001 [cluster]
root      3678     1  0 16:28 ?        00:00:00 src/redis-server *:8004 [cluster]
root      3707  2448  0 16:29 pts/0    00:00:00 grep --color=auto redis

可以看到我们的实例已经启动起来,而且进程信息上还带有[cluster]标识,记得要把你的每一个机器上的redis实例都启动起来
注意我们只是把当前的redis实例以集群模式启动,还不是真正的集群,接下来我们还要创建集群

  1. 创建Redis集群
    在开始之前请先关闭防火墙或者开放我们需要使用的端口,否则redis节点间无法通信,注意redis集群之间使用gossip协议进行通信,所以不仅要打开redis使用的端口,还要打开gossip协议使用的端口(这个端口为redis端口+10000,如8001+10000就是180001),推荐直接关闭防火墙简单省事,
    相关命令:
#关闭防火墙
systemctl stop firewalld
#开放redis端口
firewall-cmd --add-port=8001/tcp --zone=public --permanent
#开发gossip协议端口
firewall-cmd --add-port=18001/tcp --zone=public --permanent

redis-cli给我们提供了redis的集群管理的命令(redis-cli --cluster),注意这里redis5之前是使用的redis-trib.rb命令通过ruby脚本实现的集群管理,推荐使用redis5之后的版本不用在装ruby环境
你可以使用redis-cli --cluster help命令查看命令的详细介绍

[root@localhost redis-6.0.9]# src/redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
                 --cluster-fix-with-unreachable-masters
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
                 --cluster-only-masters
                 --cluster-only-replicas
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  backup         host:port backup_directory
  help

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

执行下面的命令开始创建集群

[root@localhost redis-6.0.9]# src/redis-cli -a foobared --cluster create 192.168.56.101:8001 192.168.56.102:8002 192.168.56.103:8003 192.168.56.101:8004 192.168.56.102:8005 192.168.56.103:8006 --cluster-replicas 1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> 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.56.102:8005 to 192.168.56.101:8001
Adding replica 192.168.56.103:8006 to 192.168.56.102:8002
Adding replica 192.168.56.101:8004 to 192.168.56.103:8003
M: cc32b653809f0f7539077e85f227b0c8e9ffc1d4 192.168.56.101:8001
   slots:[0-5460] (5461 slots) master
M: 948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002
   slots:[5461-10922] (5462 slots) master
M: d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003
   slots:[10923-16383] (5461 slots) master
S: 091af9ca3b6ac29f93d4e0e37ac9c69307f8e23b 192.168.56.101:8004
   replicates d12f8aca2a23f75c474920573109a094b301fdd5
S: 59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005
   replicates cc32b653809f0f7539077e85f227b0c8e9ffc1d4
S: a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006
   replicates 948d20c9b34fffc1fdbcdce9014e0f21057aa993
Can I set the above configuration? (type 'yes' to accept):

-a 指定redis的密码,create代表创建集群, 后面跟上集群中所有机器的host:port, --cluster-replicas指定集群中每个master拥有几个从节点,我们这里相当于3个主节点每个主机点一个从节点
注意:redis默认会把前三个host:port配置作为主节点,后面三个作为从节点,而且主节点的从节点不会是本机的从节点,这样可以避免单机挂掉从节点也无法使用。
运行上面的命令之后可以看到redis将创建三个master三个slave,并且每个节点都会有一个唯一的id,在master节点上我们还可以看到其对应分配的hash slot,第一个master是0-5460,以此类推,确认无误之后输入yes确认创建等待创建完成
创建成功之后会输出OK,如下

>>> Performing Cluster Check (using node 192.168.56.101:8001)
M: 99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006
   slots: (0 slots) slave
   replicates 948d20c9b34fffc1fdbcdce9014e0f21057aa993
M: d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005
   slots: (0 slots) slave
   replicates 99d0da89963166f42f1bd432d9dc7ba16f106d09
M: 948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004
   slots: (0 slots) slave
   replicates d12f8aca2a23f75c474920573109a094b301fdd5
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

至此我们的redis集群就创建完成了,接下来我们就可以连接到任意一个实例查看集群信息,
如下查看集群信息


[root@localhost redis-6.0.9]# src/redis-cli -c -a foobared -p 8001
127.0.0.1:8001> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:312
cluster_stats_messages_pong_sent:287
cluster_stats_messages_sent:599
cluster_stats_messages_ping_received:282
cluster_stats_messages_pong_received:312
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:599
127.0.0.1:8001>

参数说明
-c : 指定以集群模式运行,如果不指定该参数在设置或者读取数据的时候redis不会给我们重定向到对应的hash slot所在的节点。

使用下面的命令查看集群的节点信息,可以看到我们每一个主节点的从节点,以及每个主节点的槽位

127.0.0.1:8001> cluster nodes
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 slave 948d20c9b34fffc1fdbcdce9014e0f21057aa993 0 1604886101869 2 connected
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604886102069 3 connected 10923-16383
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604886102069 1 connected
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 master - 0 1604886100862 2 connected 5461-10922
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604886102372 3 connected
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 myself,master - 0 1604886100000 1 connected 0-5460

cluster-config-file配置说明

在上面的配置项中,我们配置了下面的nodes配置文件,该文件存放了集群的节点信息,也就是我们执行cluster nodes命令看到的信息,那他有什么用呢?加入我们的集群停掉了,下次再启动的时候就不需要再执行create去创建集群了,我们只需要把集群中的每一个节点启动起来就行了,它会读取nodes.conf配置文件中的集群信息,并加入到集群中
cluster-config-file nodes_8001.conf

用Jedis操作集群

public static void main(String[] args) {
  		// 连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(20);
        poolConfig.setMinIdle(5);
        poolConfig.setMaxIdle(10);
        // 集群机器列表
        Set<HostAndPort> jedisClusterNode = new HashSet<>();
        jedisClusterNode.add(new HostAndPort("192.168.56.101",8001));
        jedisClusterNode.add(new HostAndPort("192.168.56.101",8004));
        jedisClusterNode.add(new HostAndPort("192.168.56.102",8002));
        jedisClusterNode.add(new HostAndPort("192.168.56.102",8005));
        jedisClusterNode.add(new HostAndPort("192.168.56.103",8003));
        jedisClusterNode.add(new HostAndPort("192.168.56.103",8006));
        // 创建集群客户端
        JedisCluster jedisCluster = new JedisCluster(jedisClusterNode,6000,5000,10,"foobared",poolConfig);
        System.out.println(jedisCluster.set("cluster","zhangsan"));
        System.out.println(jedisCluster.get("cluster"));
        jedisCluster.close();
    }

槽位定位算法

cluster默认会对key使用crc16算法进行hash,然后对16384进行取模得到槽位:hash slot = crc16(key) mod 16384
上面的客户端方法比如set,get,会在内部调用JedisClusterCRC16.getSlot(key)方法,算出key所在的槽位,然后通过槽位定位到集群的节点,最后把数据写入到对应的节点,问题是客户端算出对应槽位之后怎么知道是那个主节点呢?客户端在启动的时候会把集群的节点信息也就是nodes_8001.conf文件中看到的内容加载到客户端缓存起来,这里面存储了节点的槽位信息,附上getSlot方法代码

  #JedisClusterCRC16类
  public static int getSlot(String key) {
    if (key == null) {
      throw new JedisClusterOperationException("Slot calculation of null is impossible");
    }

    key = JedisClusterHashTagUtil.getHashTag(key);
    // optimization with modulo operator with power of 2 equivalent to getCRC16(key) % 16384
    // 等价于getCRC16(key) % 16384
    return getCRC16(key) & (16384 - 1);
  }

模拟集群选举

下面我们把其中一个主节点kill掉,这里我把8002的主节点kill掉,看是否8002的从节点是否会变成主节点(我这里是8006)

[root@localhost redis-6.0.9]# ps -ef|grep redis
root      7953     1  0 11月06 ?      00:04:49 src/redis-server *:8002 [cluster]
root      7961     1  0 11月06 ?      00:04:41 src/redis-server *:8005 [cluster]
root     31976 31801  0 10:53 pts/0    00:00:00 grep --color=auto redis
[root@localhost redis-6.0.9]# kill 7953
[root@localhost redis-6.0.9]#

再看下集群的节点信息,注意看8006已经变为master了,然后8002状态变成了fail,如下:

127.0.0.1:8001> cluster nodes
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604890485738 7 connected 5461-10922
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604890485537 3 connected 10923-16383
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604890484732 1 connected
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 master,fail - 1604890448991 1604890446474 2 disconnected
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604890485234 3 connected
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 myself,master - 0 1604890484000 1 connected 0-5460

集群节点间通信机制

集群节点间通信常见的通信方式有两种分别是:集中式和gossip协议
redis集群采用的是gossip协议进行通信
集群的元数据信息:集群的节点IP,端口等信息

  1. 集中式
    集中式就是把集群节点的元数据信息放到集中的地方比如zookeeper,这种方式的优点是数据的更新读取时效性好,一旦数据变更立即更新到集中式的存储中,其他节点可以立即获取到最新信息,缺点是所有的元数据更新压力集中在一点,可能导致元数据的存储压力
  2. gossip协议
    如果说集群里某个节点挂掉了,那集群怎么知道某个节点挂了呢?节点之间通过gossip协议通信,只要其中一个节点感知到某个节点挂了,就会慢慢通知到其他节点,最后所有节点都知道这个节点挂了,这里要让最后一个节点收到失败的信息,是需要一定的时间的,所以我们一般不推荐集群的节点不要弄太多。一般5、6个主节点就可以了,
    gossip协议的多种消息,包括meet,ping,pong,fail等等,
    meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信
    ping: 每个节点都会频繁的给其他节点发送ping,其中包含自己的状态和自己维护的集群元数据,互相通过ping交换元数据,比如感知到集群节点的增加删除,hash slot信息等
    pong: 对meet和ping消息的响应,包含自己的状态和其他信息,也可以用于消息的广播和更新
    fail: 否个节点判断另一个节点fail之后看,就发送fail给其他节点,通知其他节点,指定的节点挂掉了
    gossip协议的优点在于元数据的更新比较分散,更新请求是陆续的,有一定的延迟,但是降低了压力,缺点是元数据的更新有延迟可能导致集群的一些操作滞后。
    gossip通信需要自己独立的端口,就是redis的端口加10000,每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他节点接受到ping消息之后就会响应pong消息

集群选举原理

假设下图是我们的集群在这里插入图片描述

上面我们已经知道了集群节点件的通信是依靠gossip通信的,那集群式怎么选举的呢?比如我们一台master节点挂掉了,集群怎么选举出来一个主节点?其过程如下

  1. slave发现自己的master变为fail
  2. slave节点给集群中所有节点发送FAILOVER_AUTH_REQUEST请求,请求成为master,但是这里只有集群中的master节点会回复信息
  3. 收到failover请求的master节点会回复FAIL_OVER_AUTH_ACK消息,注意这里只回复该master第一个收到failover消息的slave,最后收到最多ack的slave会成为master(这里就解释了为什么集群至少需要三个节点,如果只有两个节点,其中一个挂了是无法选举成功的)
  4. slave成为master之后,广播pong消息告诉集群中的节点我已经成为master,你们不用在选举了。
    这里有一个问题,如果两个slave同时各自收到一个ack消息怎么办呢?重新发起选举。如果每次都是同时收到两个ack怎么办呢?其实当slave发现master挂了之后并不是立即就向其他节点发起选举的而是等待一定的延迟,以保证其他的节点已经感知到该master节点挂了。如果其他节点还没有感知到master节点挂了,当slave发起选举时,其他master节点可能意识不到fail状态,就拒绝投票。
    延迟计算公式
    delay = 500ms + random(0-500ms) + SLAVE_RANK * 1000ms
    SLAVE_RANKE表示slave已经从master节点赋值数据总量的rank,rank越小代表该slave同步的数据越新,持有最新数据的slave将会首先发起选举,所以理论上选举出来的master是当前小集群中数据最新的节点,

网络抖动和脑裂问题

我们知道网络不是稳定的,可能会发生网络抖动,如果发生网络抖动,master和slave节点连接异常,这时slave可能认为master节点挂掉了,就会发起选举,这时会产生新的master节点,这个就是我们所说的脑裂问题,也就是有两个大脑同时工作,这时不同的客户端可能就会向这两个master里写入不同的数据,redis是怎么解决这个问题呢?redis内部会把脱离集群的master也就是由于网络抖动掉线的master节点变为从节点,然后把集群中选举出来的新的master的数据全部同步过来,这时脱离集群的master所有数据都会丢失,我们可以通过一个参数来解决数据丢失问题:
min-slaves-to-write 1 # 写数据时,最少成功同步写入slave的数量,相当于大于半数写成功,才算真的成功,比如我们集群总共有三个节点可以配置为1,加上master就是2,超过了半数
也就是当我们写入一个数据时,必须同时写入一个slave也成功,才能执行成功。
假如发生网络抖动,其中一个master脱离了集群,这时其他客户端是无法写入该master的,因为写入该master必须至少写入一个slave,而该master已经脱离集群,没有slave了。但是我们写入重新选举出来的master就可以成功,因为该maste还有一个slave节点可以保证写入。这种其实就是半数写入的思路
注意这个配置一定程度上会影响我们集群的可用性,比如slave要是少于一个,这个集群就算master正常也不能提供服务了,需要权衡配置,一般我们不需要配置该参数,因为我们使用redis一般是需要保证AP的(即可用性和分区容错性),如果需要强一致性需要使用zookeeper,zookeeper主要就是保证(CP,即一致性和分区容错性),CAP原理可百度
下面我们演示一下效果
我们拿8006这个节点测试,刚才我们把8006的主节点kill掉了,现在8006是主节点,但是没有从节点,我们把8006加上min-slaves-to-write 1参数并重启,然后写入数据看是否可写入

[root@localhost redis-6.0.9]# src/redis-cli -c -a foobared -p 8006
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:8006> set zhangsan3 111
(error) NOREPLICAS Not enough good replicas to write.

可以看到写入返回错误:(error) NOREPLICAS Not enough good replicas to write.
没有足够的副本节点来写入数据
下面我们把8002(刚刚关掉的master)给启动起来,这时8002会变成8006的slave, 观察下数据是否可以写入
先把8002启动,可以看到8002变成8006的从节点了

127.0.0.1:8001> cluster nodes
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604903060465 7 connected 5461-10922
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604903060564 3 connected 10923-16383
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604903059961 1 connected
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 slave a98c560d10621b7ac521bb79d7e31e44705e7caf 0 1604903060000 7 connected
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604903060965 3 connected
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 myself,master - 0 1604903060000 1 connected 0-5460

这时我们再往8006写入数据,可以看到写入成功,返回OK,

127.0.0.1:8006> set zhangsan3 111
OK
127.0.0.1:8006>

redis至少需要三个master节点

这里就不多说了,通过上面的集群选举原理我们已经知道,slave发起选举请求时,需要集群半数以上的master节点同意的,如果只有两个master,其中一个挂了,就无法选举成功,

redis集群一定要是奇数个master节点?

其实这种是否定的,假如我们有4个节点,其中一个master节点挂了,slave发起选举申请,难道另外3个节点返回同意不是半数以上吗?所以redis集群不要求非得奇数个master,但是4个节点假如挂了2个,这种也是无法选举成功的,我们部署4个节点也只能挂一个节点。所以我们最好是奇数个节点,这样可以在保证不影响选举的情况下节省一个节点,当然你多一个机器可以提高你的qps,但是并不能提高你的可用性。

redis集群对批量操作命令的支持

比如mset zhangsan 1 lisi 2 这个命令,这种批量命令是原子性的,要么所有key设置成功,要么全部失败,当我两个key映射到了不同的节点时redis就会返回error, 比如:

127.0.0.1:8001> mset zhangsan 1 lisi 1
(error) CROSSSLOT Keys in request don't hash to the same slot

可以看到执行批量设置,直接返回了错误,不能hash到相同的槽位
那如果我一定要用批量操作,让多个key在集群上操作怎么把?可以在key前面加上{xx},这样参与数据分片hash计算的只会是大括号里的值,这样就可以保证不同的key分配都同一个slot里,示例如下:

127.0.0.1:8006> mset {user1}:name zhangsan {user1}:age 20

集群运维

添加节点

redis集群是可以水平扩容的,之前我们已经知道redis是根据hash slot进行数据分片的. 那我们只需要添加一个节点到集群中,然后给该新加入的节点分配数据槽位不就可以了吗。下面我们将向集群中加入8007和8008两个节点并且一主一丛

  1. 我们先向集群中添加节点,我们这里以8007端口为例子,同样的在redis-cluster目录下新建一个8007目录(任选一台机器),然后copy一份redis.conf文件到8007下,然后我们把8007节点给启动起来(8008一样)
    使用下面的命令把8007,8008启动起来
[root@localhost redis-6.0.9]# src/redis-server redis-cluster/8007/redis.conf
[root@localhost redis-6.0.9]# src/redis-server redis-cluster/8008/redis.conf
[root@localhost redis-6.0.9]# ps -ef|grep redis
root      2391     1  0 15:27 ?        00:00:00 src/redis-server *:8001 [cluster]
root      2397     1  0 15:27 ?        00:00:00 src/redis-server *:8004 [cluster]
root      2622     1  0 15:35 ?        00:00:00 src/redis-server *:8007 [cluster]
root      2634     1  0 15:35 ?        00:00:00 src/redis-server *:8008 [cluster]
root      2640  1746  0 15:35 pts/0    00:00:00 grep --color=auto redis

可以看到我们8007和8008已经启动起来,但是这两个节点目前还是游离状态,还没有加入到集群中,下面我们先把8007加入到集群,可以使用redis-cli --cluster help查看添加节点的命令,

[root@localhost redis-6.0.9]# src/redis-cli -a foobared --cluster add-node 192.168.56.101:8007 192.168.56.101:8001
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Adding node 192.168.56.101:8007 to cluster 192.168.56.101:8001
>>> Performing Cluster Check (using node 192.168.56.101:8001)
M: 99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002
   slots: (0 slots) slave
   replicates a98c560d10621b7ac521bb79d7e31e44705e7caf
S: 59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005
   slots: (0 slots) slave
   replicates 99d0da89963166f42f1bd432d9dc7ba16f106d09
S: c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004
   slots: (0 slots) slave
   replicates d12f8aca2a23f75c474920573109a094b301fdd5
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.56.101:8007 to make it join the cluster.
[OK] New node added correctly.

看到输出[OK] New node added correctly.就表示我们的节点已经加到集群里面了。上面命令的最后一个host:port是我们集群任意一个可用节点的ip和port
原理:当我们新加入一个节点时,指定的已存在的节点会给新加入的节点发送meet消息,告诉新节点加入集群,可以看下gossip协议的meet消息。
注意:任何一个节点加入到集群默认都是master,可以执行cluster node看一下:

127.0.0.1:8002> cluster nodes
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604908242000 3 connected
eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007@18007 master - 0 1604908242000 0 connected
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604908242593 1 connected
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604908243600 7 connected 5461-10922
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604908242091 3 connected 10923-16383
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 master - 0 1604908241841 1 connected 0-5460
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 myself,slave a98c560d10621b7ac521bb79d7e31e44705e7caf 0 1604908241000 7 connected

可以看到我们的8007已经加进来了,但是这个时候还不能写入数据,前面我们已经说了,我们的master节点必须分配槽位才可以写入数据,下面我们分配槽位:

[root@localhost redis-6.0.9]# src/redis-cli -a foobared --cluster reshard 192.168.56.101:8001
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing Cluster Check (using node 192.168.56.101:8001)
M: 99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002
   slots: (0 slots) slave
   replicates a98c560d10621b7ac521bb79d7e31e44705e7caf
M: eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007
   slots: (0 slots) master
S: 59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005
   slots: (0 slots) slave
   replicates 99d0da89963166f42f1bd432d9dc7ba16f106d09
S: c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004
   slots: (0 slots) slave
   replicates d12f8aca2a23f75c474920573109a094b301fdd5
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 600
What is the receiving node ID? eff2aee84d1bc0c683066925ef7ddd31a95b68df
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: all

Ready to move 600 slots.
  Source nodes:
    M: 99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    M: a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    M: d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
  Destination node:
    M: eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007
       slots: (0 slots) master

上面命令分步解析:
2. 执行上边的命令会先问我们你想迁移多少个槽位?我们输入600
3. 然后继续询问把这600个槽位分配给那个节点?我们输入8007这个节点的节点id
4. 然后又会询问我们从那些节点里迁移600个槽位?我们输入all,表示把所有的节点作为源节点,当然你也可以指定某几个节点作为源节点
5. 这时会给我们列出所有的槽位迁移信息,我们确认无误输入yes,等待迁移完成
待迁移完成我们再看一下集群的槽位信息:

127.0.0.1:8002> cluster nodes
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604909151757 3 connected
eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007@18007 master - 0 1604909150046 8 connected 0-198 5461-5661 10923-11121
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604909150000 1 connected
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604909151556 7 connected 5662-10922
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604909150751 3 connected 11122-16383
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 master - 0 1604909150000 1 connected 199-5460
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 myself,slave a98c560d10621b7ac521bb79d7e31e44705e7caf 0 1604909149000 7 connected

注意看8007这个节点后面的槽位信息,现在我们的8007节点已经有600个槽位,而且是从其他三个节点里分别抽取出来一部分(200个)作为8007的槽位,注意槽位对应的数据也会迁移过来
下面我们把8008节点也加入进来,并且设置为8007的从节点,加入8008同样执行add-node命令,这里不再演示,加入进来之后我们看一下集群信息,可以看到8008也加入进来了,但是也是master节点,注意这里不要给8008分配槽位信息。

127.0.0.1:8002> cluster nodes
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604909460000 3 connected
eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007@18007 master - 0 1604909460443 8 connected 0-198 5461-5661 10923-11121
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604909459540 1 connected
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604909460000 7 connected 5662-10922
a3f65ad46abb20ee45c504ec6bce3a848a46fb1b 192.168.56.101:8008@18008 master - 0 1604909460000 0 connected
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604909461447 3 connected 11122-16383
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 master - 0 1604909461146 1 connected 199-5460
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 myself,slave a98c560d10621b7ac521bb79d7e31e44705e7caf 0 1604909457000 7 connected

把8008配置为8007的从节点,我们需要打开8008的redis客户端,然后在客户端里执行下面的命令:


[root@localhost redis-6.0.9]# src/redis-cli -c -p 8008 -a foobared
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:8008> cluster replicate eff2aee84d1bc0c683066925ef7ddd31a95b68df
OK
127.0.0.1:8008>

cluster replicate eff2aee84d1bc0c683066925ef7ddd31a95b68df # 该命令后的node id是8007也就是master的节点id
我们再看下集群信息,可以看到8008已经成为8007的从节点了,可以在8007设置一个key看下数据。


127.0.0.1:8008> cluster nodes
59889d641351aa636b76e0a46dcb45176efdd6a7 192.168.56.102:8005@18005 slave 99d0da89963166f42f1bd432d9dc7ba16f106d09 0 1604909773559 1 connected
c69e341e245e178a409a927adea02021b647555b 192.168.56.101:8004@18004 slave d12f8aca2a23f75c474920573109a094b301fdd5 0 1604909773057 3 connected
99d0da89963166f42f1bd432d9dc7ba16f106d09 192.168.56.101:8001@18001 master - 0 1604909773559 1 connected 199-5460
eff2aee84d1bc0c683066925ef7ddd31a95b68df 192.168.56.101:8007@18007 master - 0 1604909773962 8 connected 0-198 5461-5661 10923-11121
a98c560d10621b7ac521bb79d7e31e44705e7caf 192.168.56.103:8006@18006 master - 0 1604909774000 7 connected 5662-10922
948d20c9b34fffc1fdbcdce9014e0f21057aa993 192.168.56.102:8002@18002 slave a98c560d10621b7ac521bb79d7e31e44705e7caf 0 1604909774000 7 connected
d12f8aca2a23f75c474920573109a094b301fdd5 192.168.56.103:8003@18003 master - 0 1604909772958 3 connected 11122-16383
a3f65ad46abb20ee45c504ec6bce3a848a46fb1b 192.168.56.101:8008@18008 myself,slave eff2aee84d1bc0c683066925ef7ddd31a95b68df 0 1604909772000 8 connected

到此我们把redis集群模式从搭建到原理已经做了详细的讲解。

注意: 默认情况下,集群中的一个小集群全部挂掉即一个master和该master的所有slave都挂掉了,那整个集群都会不可用,可以通过一个配置修改
cluster-require-full-coverage no # 表示当负责一个hash槽的master下线并且没有slave进行failover时,集群仍然可用,如果为yes集群不可用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值