深刻学习Redis之Redis Cluster

Redis Cluster

在学习Redis Cluster以前,咱们先了解为何须要集群,当遇到单机内存、并发、流量等瓶颈时,单机已经没法知足我让节点7000和7001等节点进们的要求的时候,能够采用Cluster架构方案达到负载均衡的目的。node

数据分区概论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每一个节点负责总体数据的一个子集。redis

常见的分区规则有哈希分区和顺序分区两种。shell

首先看一下对比数据库

分布方式 特色 典型产品
哈希分区 数据分散度高、键值分布无业务无关、没法顺序访问、支持批量操做。 一致性哈希:Mecache、Redis Cluster ...
顺序分区 数据分散度易倾斜、键值业务相关、能够顺序访问、支持批量操做。 BigTable、HBase

顺序分区

好比:1-100个数字,要保存到3个节点上,每一个节点平均存储,1-33存储在第1个节点,34-66存储到2节点,剩余存储到3节点。缓存

顺序存储经常使用在关系型存储上。架构

哈希分区

由于Redis Cluster采用的哈希分区,因此咱们看一下常见的哈希分区有哪几种。并发

节点取余分区

好比100个数据,对每一个数据进行hash运算以后,再于节点数进行取余运算,根据余数保存在不一样节点上。负载均衡

缺点就是:当节点数量变化时,如扩容或收缩节点,数据节点映射关系须要从新计算,会致使数据的从新迁移。运维

一致性哈希分区

为系统中每一个节点分配一个token,范围通常在0~2的32次方,这些token构成一个哈希环。数据读写执行节点查找操做时,先根据key计算hash值,而后顺时针找到第一个大于等于该哈希值的token节点,以下图所示异步

这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其余节点无影响

但一致性哈希也存在一些问题:

  • 加减节点会形成哈希环中部分数据没法命中(例如一个key增减节点前映射到第n2个节点,所以它的数据是保存在第n2个节点上的;当咱们增长一个节点后被映射到n5节点上了,此时咱们去n5节点上去找这个key对应的值是找不到的,见下图),须要手动处理或者忽略这部分数据,所以一致性哈希经常使用于缓存场景。

  • 当使用少许节点时,节点变化将大范围影响哈希环中数据映射,所以这种方式不适合少许数据节点的分布式方案。
  • 普通的一致性哈希分区在增减节点时须要增长一倍或减去一半节点才能保证数据和负载的均衡。

虚拟槽分区

Redis Cluster采用的就是虚拟槽分区。槽的范围是0~16383,将16384个槽平均分配给节点,由节点进行管理。

每次将key进行hash运算,对16383进行取余,而后去redis对应的槽进行查找。

槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每一个节点会负责必定数量的槽。

好比咱们如今有5个集群,每一个节点平均大约负责3276个槽。Redis Cluster 计算公式:slot=CRC16(key)&16383。每个节点负责维护一部分槽以及槽所映射的键值数据。

Redis虚拟槽分区的特色

  • 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
  • 节点自身维护槽的映射关系,不须要客户端或者代理服务维护槽分区元数据。
  • 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。

准备节点

Redis集群通常由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。每一个节点须要开启配置cluster-enabled yes,让Redis运行在集群模式下。

首先咱们在redis文件中建立三个文件夹:configdatalog。分别存放配置、数据和日志相关文件。

配置相关redis.conf

#节点端口
port ${port}
 
# 守护进程模式启动(可选)
daemonize yes
 
# 开启集群模式
cluster-enabled yes
 
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
 
# 集群内部配置文件
cluster-config-file /usr/local/redis/config/nodes-${port}.conf

# 节点宕机后是否整个集群不可用
cluster-require-full-coverage no

dir /usr/local/redis/data/

dbfilename dump-${port}.rdb

logfile ${port}.log

# 其他的配置与redis.conf默认配置文件一致便可

6个节点所有配完成后就能够开启了。

[root@localhost config]# ls
redis-7000.conf  redis-7001.conf  redis-7002.conf  redis-7003.conf  redis-7004.conf  redis-7005.conf
[root@localhost redis]# redis-server config/redis-7000.conf
[root@localhost redis]# cd config
[root@localhost config]# cat nodes-7000.conf
f4deba14aac6494e95e3e4ad060c94b8c82df7ec :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
[root@localhost config]# cd ..
[root@localhost redis]# redis-server config/redis-7001.conf
[root@localhost redis]# redis-server config/redis-7002.conf
[root@localhost redis]# redis-server config/redis-7003.conf
[root@localhost redis]# redis-server config/redis-7004.conf
[root@localhost redis]# redis-server config/redis-7005.conf
[root@localhost redis]# cd config
[root@localhost config]# ll
总用量 288
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7000.conf
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7001.conf
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7002.conf
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7003.conf
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7004.conf
-rw-r--r--. 1 root root   112 12月 17 04:00 nodes-7005.conf
-rw-r--r--. 1 root root 41650 12月 17 03:59 redis-7000.conf
-rw-r--r--. 1 root root 41649 12月 17 03:59 redis-7001.conf
-rw-r--r--. 1 root root 41651 12月 17 03:59 redis-7002.conf
-rw-r--r--. 1 root root 41651 12月 17 03:59 redis-7003.conf
-rw-r--r--. 1 root root 41651 12月 17 03:59 redis-7004.conf
-rw-r--r--. 1 root root 41651 12月 17 03:59 redis-7005.conf
[root@localhost config]# cat nodes-7005.conf
d1e8e8e42be8d3b2f3f44d197138e54d91170442 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
[root@localhost config]#

检查节点日志是否正确:

sudo cat /usr/local/redis/conf/nodes-${port}.conf

文件内容记录了集群初始状态,这里最重要的是节点ID,它是一个40位16进制字符串,用于惟一标识集群内一个节点,以后不少集群操做都要借助于节点ID来完成。须要注意是,节点ID不一样于运行ID:节点ID在集群初始化 时只建立一次,节点重启时会加载集群配置文件进行重用,而Redis的运行ID每次重启都会变化。

咱们如今启动6个节点,但每一个节点彼此并不知道对方的存在,下面经过节点握手让6个节点彼此创建联系从而组成一个集群

[root@localhost redis]# ps -ef |grep redis
root      1388     1  0 09:10 ?        00:00:00 redis-server *:7000 [cluster]
root      1392     1  0 09:10 ?        00:00:00 redis-server *:7001 [cluster]
root      1396     1  0 09:10 ?        00:00:00 redis-server *:7002 [cluster]
root      1400     1  0 09:10 ?        00:00:00 redis-server *:7003 [cluster]
root      1404     1  0 09:10 ?        00:00:00 redis-server *:7004 [cluster]
root      1408     1  0 09:10 ?        00:00:00 redis-server *:7005 [cluster]

节点握手

节点握手是指一批运行在集群模式下的节点经过Gossip协议彼此通讯, 达到感知对方的过程

节点握手是集群彼此通讯的第一步,由客户端发起下面的命令:

cluster meet {ip} {port}
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7002
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7003
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7004
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7005
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7006
OK

上面执行命令以后让节点7000和7001等节点进行握手通讯。cluster meet命令是一个异步命令,执行以后马上返回。内部发起与目标节点进行握手通讯。

  • 节点7000本地建立7001节点信息对象,并发送meet消息。
  • 节点7001接受到meet消息后,保存7000节点信息并回复pong消息。
  • 以后节点7000和7001彼此按期经过ping/pong消息进行正常的节点通讯。

这个时候咱们再执行cluster nodes能够看到已经检测到其它节点了。

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 master - 0 1609463858135 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609463860149 1 connected
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 master - 0 1609463857127 3 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 master - 0 1609463859143 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609463861156 2 connected

节点创建握手以后集群还不能正常工做,这时集群处于下线状态,全部的数据读写都被禁止。经过以下命令能够看到:

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000
127.0.0.1:7000> set jack hello
(error) CLUSTERDOWN The cluster is down

经过cluster info命令能够获取集群当前状态:

127.0.0.1:7000> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_sent:670
cluster_stats_messages_received:521

能够看到咱们如今的状态是fail,被分配的槽 cluster_slots_assigned是0,因为目前全部的槽没有分配到节点,所以集群没法完成槽到节点的映射。只有当16384个槽所有分配给节点后,集群才进入在线状态

分配槽

Redis集群把全部的数据映射到16384个槽中。每一个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。经过cluster addslots命令为节点分配槽。由于咱们有6个节点,咱们是三主三从的模式,因此只用给三个主节点进行配置便可。

redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0..5461}
redis-cli -h 127.0.0.1 -p 7001 cluster addslots {5462..10922}
redis-cli -h 127.0.0.1 -p 7002 cluster addslots {10923..16383}

配置成功后,咱们再进入节点看一下:

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000
127.0.0.1:7000> 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:5
cluster_my_epoch:0
cluster_stats_messages_sent:1384
cluster_stats_messages_received:1235

能够看到,cluster_statecluster_slots_assigned都没有问题。

设置主从

目前还有三个节点没有使用,做为一个完整的集群,每一个负责处理槽的节点应该具备从节点,保证当它出现故障时能够自动进行故障转移。

集群模式下,Reids节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。使用cluster replicate {node-id}命令让一个节点成为从节点。其中命令执行必须在对应的从节点上执行,node-id是要复制主节点的节点ID。

咱们首先找到三个已经配置槽的节点的node-id。

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 master - 0 1609464545892 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609464547906 1 connected 5462-10922
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 master - 0 1609464546899 3 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 master - 0 1609464549923 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609464548916 2 connected 10923-16383
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7003 cluster replicate f4deba14aac6494e95e3e4ad060c94b8c82df7ec
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7004 cluster replicate 9a8abb84bcc8301a8f11c664471159dc0bf23a62
OK
[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7005 cluster replicate d438b4689776cb6cd6b6d0eaecb7576669c7b3fe
OK

完成后咱们查看是否已经ok。

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 slave d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 0 1609464847442 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609464846435 1 connected 5462-10922
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 slave f4deba14aac6494e95e3e4ad060c94b8c82df7ec 0 1609464849456 3 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 slave 9a8abb84bcc8301a8f11c664471159dc0bf23a62 0 1609464848449 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609464850468 2 connected 10923-16383

目前为止,咱们依照Redis协议手动创建一个集群。它由6个节点构成, 3个主节点负责处理槽和相关数据,3个从节点负责故障转移。

Redis自动化安装

咱们以前分别使用命令搭建了一个完整的集群,可是命令过多,当集群节点众多时,必然会加大搭建集群的复杂度和运维成本。所以redis还提供了redis-cli --cluster来搭建集群

首先咱们仍是启动六个单独的节点。

使用下面命令进行安装,--cluster-replicas 1 指定集群中每一个主节点配备几个从节点,这里设置为1。而且该命令会本身建立主节点和分配从节点,其中前3个是主节点,后3个是从节点,后3个从节点分别复制前3个主节点。

redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 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

最后的输出报告说明:16384个槽所有被分配,集群建立成功。这里须要注意命令中节点的地址必须是不包含任何槽/数据的节点,不然会拒绝建立集群。

若是不想要从节点则不填写该参数便可--cluster-replicas 1

最后咱们可使用下面命令进行查看是否已经ok。

redis-cli --cluster check 127.0.0.1:7000

集群伸缩原理

Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的状况下,能够为集群添加节点进行扩容也能够下线部分节点进行缩容。原理可抽象为槽和对应数据在不一样节点之间灵活移动。

当咱们如今有三个节点,此时想增长6385节点,也就是每一个节点把一部分槽和数据迁移到新的节点6385,每一个节点负责的槽和数据相比以前变少了从而达到了集群扩容的目的。

扩容集群实操

准备节点

以前咱们有6个节点,7000~7005节点。

如今咱们增长两个单独的节点也就是7006和7007。而后7006节点当作主节点,7007当作从节点。新节点跟集群内的节点配置保持一致,便于管理统一。

随后咱们进行启动

[root@localhost redis]# redis-server config/redis-7006.conf
[root@localhost redis]# redis-server config/redis-7007.conf

这个时候咱们的两个新的节点只是单独运行,并无加入集群中。能够看到下面并无7006和7007节点。

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 slave d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 0 1609467765084 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609467769137 1 connected 5462-10922
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 slave f4deba14aac6494e95e3e4ad060c94b8c82df7ec 0 1609467767119 3 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 slave 9a8abb84bcc8301a8f11c664471159dc0bf23a62 0 1609467768127 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609467766110 2 connected 10923-16383

结构图以下:

加入集群

redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7006
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7007

集群内新旧节点通过一段时间的ping/pong消息通讯以后,全部节点会发现新节点并将它们的状态保存到本地。

随后咱们再进行查看cluster nodes

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 slave d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 0 1609468208783 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609468204768 1 connected 5462-10922
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 slave f4deba14aac6494e95e3e4ad060c94b8c82df7ec 0 1609468210798 3 connected
35f9f0abd365bb0fc424dbdaa849f1f1c71163bb 127.0.0.1:7006 master - 0 1609468209790 6 connected
55b028fbd0a0207b6acc6e2b1067bf79f3090534 127.0.0.1:7007 master - 0 1609468206777 7 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 slave 9a8abb84bcc8301a8f11c664471159dc0bf23a62 0 1609468205773 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609468206274 2 connected 10923-16383

而后咱们把7007设置为7006的从节点

redis-cli -h 127.0.0.1 -p 7007 cluster replicate 35f9f0abd365bb0fc424dbdaa849f1f1c71163bb

再次查看已经OK。

[root@localhost redis]# redis-cli -h 127.0.0.1 -p 7000 cluster nodes
d1e8e8e42be8d3b2f3f44d197138e54d91170442 127.0.0.1:7005 slave d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 0 1609470748800 4 connected
9a8abb84bcc8301a8f11c664471159dc0bf23a62 127.0.0.1:7001 master - 0 1609470750824 1 connected 5462-10922
f4deba14aac6494e95e3e4ad060c94b8c82df7ec 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
d5f317fc4597dbaac8b26a5897d801a72e45512e 127.0.0.1:7003 slave f4deba14aac6494e95e3e4ad060c94b8c82df7ec 0 1609470745778 3 connected
35f9f0abd365bb0fc424dbdaa849f1f1c71163bb 127.0.0.1:7006 master - 0 1609470746785 6 connected
55b028fbd0a0207b6acc6e2b1067bf79f3090534 127.0.0.1:7007 slave 35f9f0abd365bb0fc424dbdaa849f1f1c71163bb 0 1609470751833 7 connected
7dbbf232c72405a66416d2a0c335bd072f740644 127.0.0.1:7004 slave 9a8abb84bcc8301a8f11c664471159dc0bf23a62 0 1609470749817 5 connected
d438b4689776cb6cd6b6d0eaecb7576669c7b3fe 127.0.0.1:7002 master - 0 1609470747795 2 connected 10923-16383

槽迁移计划

上面咱们添加了两个新节点:700六、7007。其中7006做为主节点存储数据,7007做为从节点复制7006。下面咱们要把其余节点的槽和数据迁移到7006这个节点中。

再迁移后原有节点负责的槽数量变为4096个。

迁移数据

数据迁移过程是逐个槽进行的。流程以下:

  1. 对目标节点发送:cluster setslot {slot} importing {sourceNodeId}命令,让目标节点准备导入槽数据。
  2. 对源节点发送:cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽数据。
  3. 源节点循环执行:cluster getkeysinslot {slot} {count}命令,每次获取count个属于槽的键。
  4. 在源节点上执行:migrate {targetIP} {targetPort} key 0 {timeout}命令,把指定的key迁移。
  5. 重复执行步骤3和步骤4,直到槽下全部的键值数据迁移到目标节点。
  6. 向集群内全部主节点发送:cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。

伪代码以下:

def move_slot(source,target,slot):
    # 目标节点准备导入槽
    target.cluster("setslot",slot,"importing",source.nodeId);
    # 源节点准备全出槽
    source.cluster("setslot",slot,"migrating",target.nodeId);
    while true :
        # 批量从源节点获取键
        keys = source.cluster("getkeysinslot",slot,pipeline_size);
        if keys.length == 0:
            # 键列表为空时,退出循环
            break;
        # 批量迁移键到目标节点
        source.call("migrate",target.host,target.port,"",0,timeout,"keys",keys);
    # 向集群全部主节点通知槽被分配给目标节点
    for node in nodes:
        if node.flag == "slave":
            continue;
        node.cluster("setslot",slot,"node",target.nodeId);

redis-cli cluster进行迁移

redis-cli --cluster reshard host:port --from <arg> --to <arg> --slots <arg> --yes --timeout
<arg> --pipeline <arg>
  • host:port:必传参数,集群内任意节点地址,用来获取整个集群信息。
  • --from:制定源节点的id,若是有多个源节点,使用逗号分隔,若是是all源节点变为集群内全部主节点,在迁移过程当中提示用户输入。
  • --to:须要迁移的目标节点的id,目标节点只能填写一个,在迁移过程 中提示用户输入。
  • --slots:须要迁移槽的总数量,在迁移过程当中提示用户输入。
  • --yes:当打印出reshard执行计划时,是否须要用户输入yes确认后再执行reshard。
  • --timeout:控制每次migrate操做的超时时间,默认为60000毫秒。
  • ·--pipeline:控制每次批量迁移键的数量,默认为10。

开始迁移:

redis-cli --cluster reshard 127.0.0.1:7000

输入须要迁移的槽数量,此处咱们输入4096。

目标节点ID,只能指定一个,由于咱们须要迁移到7006中,所以下面输入7006的ID。

以后输入源节点的ID,redis会从这些源节点中平均取出对应数量的槽,而后迁移到6385中,下面咱们分别输入7000、700一、7002的节点ID。最后要输入done表示结束。

最后输入yes便可。

咱们能够检查一下节点之间的平衡性

redis-cli --cluster rebalance 127.0.0.1:6380

全部主节点负责的槽数量差别在2%之内,就算集群节点数据相对均匀,无需调整

收缩集群

  • 首先须要肯定下线节点是否有负责的槽,若是是,须要把槽迁移到 其余节点,保证节点下线后整个集群槽节点映射的完整性。
  • 当下线节点再也不负责槽或者自己是从节点时,就能够通知集群内其 他节点忘记下线节点,当全部的节点忘记该节点后能够正常关闭。

收缩正好和扩容迁移方向相反,7006变为源节点,其余主节点变为目标节点,源节点须要把自身负责的4096个槽均匀地迁移到其余主节点上。

具体步骤和上述扩容相似,这里就不演示。

请求重定向

在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,若是节点是自身,则处理键命令;不然回复MOVED重定向错误,通知客户端请求正确的节点。

命中槽

由于咱们执行cluster keyslot hello以后,发现槽的位置在866,在咱们之中,因此直接返回。

127.0.0.1:7000> set hello world
OK
127.0.0.1:7000> cluster keyslot hello
(integer) 866
127.0.0.1:7000> get hello
"world"

未命中槽

因为键对应槽是6918,不属于7000节点,则回复MOVED {slot} {ip} {port}格式重定向信息:

127.0.0.1:7000> set test hello
(error) MOVED 6918 127.0.0.1:7001

咱们能够切换到7001发送命令便可成功。

127.0.0.1:7001> set test hello
OK

用redis-cli命令时,能够加入-c参数支持自动重定向,简化手动发起重定向操做。

[root@localhost config]# redis-cli -h 127.0.0.1 -p 7000 -c
127.0.0.1:7000> set test hello
-> Redirected to slot [6918] located at 127.0.0.1:7001
OK

ASK重定向

Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程当中,客户端须要作到智能识别,保证键命令可正常执行。例如当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另外一部分在目标节点。

当出现上述状况时,客户端键命令执行流程将发生变化,以下所示:

  1. 客户端根据本地slots缓存发送命令到源节点,若是存在键对象则直 接执行并返回结果给客户端。
  2. 若是键对象不存在,则可能存在于目标节点,这时源节点会回复 ASK重定向异常。格式以下:(error) ASK {slot} {targetIP}:{targetPort}
  3. 客户端从ASK重定向异常提取出目标节点信息,发送asking命令到目标节点打开客户端链接标识,再执行键命令。若是存在则执行,不存在则返回不存在信息。

ASK和MOVED区别

  • ASK重定向说明集群正在进行slot数据迁移,客户端没法知道何时迁移完成,所以只能是临时性的重定向,客户端不会更新slots缓存。
  • 可是MOVED重定向说明键对应的槽已经明确指定到新的节点,所以须要更新slots缓存。

故障发现

  • 当集群内某个节点出现问题时,须要经过一种健壮的方式保证识别出节点是否发生了故障。Redis集群内节点经过ping/pong消息实现节点通讯,消息不但能够传播节点槽信息,还能够传播其余状态如:主从状态、节点故障等。
  • 所以故障发现也是经过消息传播机制实现的,主要环节包括:

    • 主观下线 (pfail):指某个节点认为另外一个节点不可用,即下线状态,这个状态并非最终的故障断定,只能表明一个节点的意见,可能存在误判状况。
    • 客观下线(fail):指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。若是是持有槽的主节点故障,须要为该节点进行故障转移。

主观下线

集群中每一个节点都会按期向其余节点发送ping消息,接收节点回复pong消息做为响应。若是在cluster-node-timeout时间内通讯一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态

主观下线流程:

  1. 节点a发送ping消息给节点b,若是通讯正常将接收到pong消息,节点a更新最近一次与节点b的通讯时间。
  2. 若是节点a与节点b通讯出现问题则断开链接,下次会进行重连。若是一直通讯失败,则节点a记录的与节点b最后通讯时间将没法更新。
  3. 节点a内的定时任务检测到与节点b最后通讯时间超过cluster-nodetimeout时,更新本地对节点b的状态为主观下线(pfail)。

客观下线

当半数以上持有槽的主节点都标记某节点主观下线。

客观下线流程:

  1. 当消息体内含有其余节点的pfail状态会判断发送节点的状态,若是发送节点是主节点则对报告的pfail状态处理,从节点则忽略
  2. 找到pfail对应的节点结构,更新clusterNode内部下线报告链表。
  3. 根据更新后的下线报告链表告尝试进行客观下线。

尝试客观下线

  1. 首先统计有效的下线报告数量,若是小于集群内持有槽的主节点总数的一半则退出
  2. 当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
  3. 向集群广播一条fail消息,通知全部的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID。

故障恢复

故障节点变为客观下线后,若是下线节点是持有槽的主节点则须要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的全部从节点承担故障恢复的义务,当从节点经过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。

检查资格

  • 每一个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点
  • 若是从节点与主节点断线时间超过cluster-node-timeout * cluster-slave-validity-factor,则当前从节点不具有故障转移资格。参数cluster-slavevalidity-factor用于从节点的有效因子,默认为10。

准备选举时间

当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程

主节点b进入客观下线后,它的三个从节点根据自身复制偏移量设置延迟选举时间,如复制偏移量最大的节点slave b-1延迟1秒执行,保证复制延迟低的从节点优先发起选举。

选举投票

  • 只有持有槽的主节点才会处理故障选举消息。
  • 投票过程实际上是一个领导者选举的过程,如集群内有N个持有槽的主节点表明有N张选票。因为在每一个配置纪元内持有槽的主节点只能投票给一个从节点,所以只能有一个从节点得到N/2+1的选票,保证可以找出惟一的从节点。
  • Redis集群没有直接使用从节点进行领导者选举,主要由于从节点数必须大于等于3个才能保证凑够N/2+1个节点,将致使从节点资源浪费。使用集群内全部持有槽的主节点进行领导者选举,即便只有一个从节点也能够完成选举过程。
  • 当从节点收集到N/2+1个持有槽的主节点投票时,从节点能够执行替换主节点操做,例如集群内有5个持有槽的主节点,主节点b故障后还有4个, 当其中一个从节点收集到3张投票时表明得到了足够的选票能够进行替换主节点操做。

替换主节点

当从节点收集到足够的选票以后,触发替换主节点操做:

  • 当前从节点取消复制变为主节点
  • 执行clusterDelSlot操做撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽委派给本身。
  • 向集群广播本身的pong消息,代表已经替换了故障从节点。
相关文章
相关标签/搜索