在 Redis 如何使用 Twemproxy 和 Sentinel 构建高可用集群架构? 篇中介绍了构建 Redis 集群的一种平替方案,但是 Twemproxy + Sentinel 方案有着其自身的缺点,其中最主要的是在节点的伸缩时,数据集的自动平衡是个比较棘手的问题。这篇文章介绍 Redis 官方提供的构建集群方案。通过本篇可以知道 Redis Cluster 如何构建、Redis 节点伸缩的操作方式。
Redis Cluster 提供 Redis 的水平扩展的能力,采用去中心化的架构,集群中的每个节点保存独立的数据以及整个集群的相关状态。由于每个节点都和集群中的其他节点进行交互,客户端只要链接其中一个节点,就能感知整个集群节点。
Redis Cluster 对于节点数据的分片并没有采用传统的一致性哈希算法,而是采用了一种不同的分片方式,叫做哈希槽。具体就是在 Redis Cluster 中有 16384 个哈希槽,对一个给定的 key 进行操作时,会对该 key 应用 CRC16 算法然后与 16384 进行取模运算,进而判断该 key 位于哪个哈希槽上。
使用哈希槽设计有诸多好处:
Redis Cluster 不保证强一致性的事务,由于 Cluster 的基础是异步复制,所以会有一个时间的窗口导致数据丢失。
以下图的拓扑结构为例,构建 Redis Cluster 集群。
以下配置,仅仅表示我的配置环境,读者可以根据自己的环境进行配置。
注意:需要把主机的防火墙关闭或者把 6379,6380,16379,16380 端口对外放开。
这里使用源码进行构建。
# 登陆 10.211.55.6 主机,安装 Redis
cd /opt
wget http://download.redis.io/releases/redis-5.0.9.tar.gz
tar -xvf redis-5.0.9.tar.gz
cd redis-5.0.9
make && make install
使用如下命令进行构建。
# 配置 A 节点
# 在 /usr/local/etc 目录下创建 redis-cluster/redis-6379 目录,用来存放集群所需的配置文件
mkdir -p /usr/local/etc/redis-cluster/redis-6379
# 创建 redis.conf 文件,写入如下内容,保存退出
vim /usr/local/etc/redis-cluster/redis-6379/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
cluster-enabled yes
dir /usr/local/etc/redis-cluster/redis-6379
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
# 启动 A 节点
nohup redis-server /usr/local/etc/redis-cluster/redis-6379/redis.conf > /dev/null 2>&1 &
# 配置 A1 节点
# 在 /usr/local/etc 目录下创建 redis-cluster/redis-6380 目录,用来存放集群所需的配置文件
mkdir -p /usr/local/etc/redis-cluster/redis-6380
# 创建 redis.conf 文件,写入如下内容,保存退出
vim /usr/local/etc/redis-cluster/redis-6380/redis.conf
port 6380
bind 0.0.0.0
protected-mode no
cluster-enabled yes
dir /usr/local/etc/redis-cluster/redis-6380
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
# 启动 A1 节点
nohup redis-server /usr/local/etc/redis-cluster/redis-6380/redis.conf > /dev/null 2>&1 &
构建的方式同 A,A1,只是主机为 10.211.55.7,具体步骤不再赘述。
构建的方式同 A,A1,只是主机为 10.211.55.8,具体步骤不再赘述。
redis-cli --cluster create 10.211.55.6:6379 10.211.55.7:6379 10.211.55.8:6379 --cluster-replicas 0
# 执行上面的命令,会输出类似如下内容
>>> Performing hash slots allocation on 3 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
M: 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379
slots:[0-5460] (5461 slots) master
M: a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379
slots:[5461-10922] (5462 slots) master
M: 35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379
slots:[10923-16383] (5461 slots) master
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 10.211.55.6:6379)
M: 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379
slots:[0-5460] (5461 slots) master
M: 35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379
slots:[10923-16383] (5461 slots) master
M: a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379
slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
通过上面的输出,可以看到每个节点的哈希槽分配情况,分别是:
可以看到每个主节点的状态,包括 Node Id,地址以及端口,主/从节点标志,时间戳以及分配的哈希槽范围。
注意此处的 Node Id,这是自动生成的,在不同的服务器上面会有不同的值。
# 查看集群节点状态信息
redis-cli -p 6379 cluster nodes
# 输出
35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379@16379 master - 0 1681214336434 3 connected 10923-16383
a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379@16379 master - 0 1681214335403 2 connected 5461-10922
5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379@16379 myself,master - 0 1681214336000 1 connected 0-5460
可以看到在进行操作时,会重定向到实际的哈希槽上面。
# 客户端登陆
redis-cli -c -p 6379
# 进行 set 操作,观看哈希槽的重新定向
127.0.0.1:6379> set foo bar
-> Redirected to slot [12182] located at 10.211.55.8:6379
OK
10.211.55.8:6379> set hello world
-> Redirected to slot [866] located at 10.211.55.6:6379
OK
在上面创建集群时,没有把从节点加进去,可以采用如下命令,给每个主节点添加一个从节点。
# 将 10.211.55.6:6380 添加为 10.211.55.6:6379 的从节点,注意后面 cluster-master-id 是集群节点 10.211.55.6:6379 的 Node Id
redis-cli --cluster add-node 10.211.55.6:6380 10.211.55.6:6379 --cluster-slave --cluster-master-id 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad
# 将 10.211.55.7:6380 添加为 10.211.55.7:6379 的从节点,注意后面 cluster-master-id 是集群节点 10.211.55.7:6379 的 Node Id
redis-cli --cluster add-node 10.211.55.7:6380 10.211.55.7:6379 --cluster-slave --cluster-master-id a1a7be53d2ebc9d54fb18c59f31171cf6d371f29
# 将 10.211.55.8:6380 添加为 10.211.55.8:6379 的从节点,注意后面 cluster-master-id 是集群节点 10.211.55.8:6379 的 Node Id
redis-cli --cluster add-node 10.211.55.8:6380 10.211.55.8:6379 --cluster-slave --cluster-master-id 35c17301dac6d01cdd06b01c449c895e1d5f6028
通过如下的输出可以看到,每个主机上 6380 端口的 Redis 实例成为对应的 6379 端口的 Redis 实例的从节点。
redis-cli -p 6379 cluster nodes
# 输出
e667c40572bb7a34fae03ee407e0849728537497 10.211.55.8:6380@16380 slave 35c17301dac6d01cdd06b01c449c895e1d5f6028 0 1681215954316 3 connected
a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379@16379 master - 0 1681215953285 2 connected 5461-10922
35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379@16379 master - 0 1681215955346 3 connected 10923-16383
d070ed606c2067bca37f7a079971eed5d67b8841 10.211.55.6:6380@16380 slave 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 0 1681215954517 1 connected
32f44e3aab49194db8e5e819a1dc851a86a1b584 10.211.55.7:6380@16380 slave a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 0 1681215954000 2 connected
5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379@16379 myself,master - 0 1681215954000 1 connected 0-5460
Redis 可以通过重新分片(Reshard)来动态伸缩节点。需要注意的是,在给集群增加节点时,需要进行哈希槽的重新分配,以便能够达到新的平衡;在给集群移除节点时,需要对移除的节点执行哈希槽的移动,使其成为一个空的主节点,再调用相关命令将该节点移除。
可以通过如下指令进行哈希槽的重新分片。
redis-cli --cluster reshard [new node ip]:[new node port] --cluster-from [from node id] --cluster-to [to node id] --cluster-slots [number of slots] --cluster-yes
例如,将 10.211.55.6:6379 的哈希槽全部转移到 10.211.55.7:6379 节点上,可以执行如下命令:
redis-cli --cluster reshard 10.211.55.6:6379 --cluster-from 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad --cluster-to a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 --cluster-slots 5461 --cluster-yes
# 输出
...
Moving slot 5454 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5455 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5456 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5457 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5458 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5459 from 10.211.55.6:6379 to 10.211.55.7:6379:
Moving slot 5460 from 10.211.55.6:6379 to 10.211.55.7:6379:
# 观察现在 master 节点的哈希槽的分配情况
redis-cli -p 6379 cluster nodes
# 可以看到 10.211.55.6:6379 已经没有了哈希槽,而 10.211.55.7:6379 的哈希槽范围变成了 [0-10922]
e667c40572bb7a34fae03ee407e0849728537497 10.211.55.8:6380@16380 master - 0 1681222117588 11 connected 10923-16383
a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379@16379 master - 0 1681222117000 13 connected 0-10922
35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379@16379 slave e667c40572bb7a34fae03ee407e0849728537497 0 1681222117892 11 connected
d070ed606c2067bca37f7a079971eed5d67b8841 10.211.55.6:6380@16380 slave a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 0 1681222117000 13 connected
32f44e3aab49194db8e5e819a1dc851a86a1b584 10.211.55.7:6380@16380 slave a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 0 1681222116557 13 connected
5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379@16379 myself,master - 0 1681222116000 8 connected
# 执行 rebalance 命令
redis-cli --cluster rebalance --cluster-use-empty-masters 10.211.55.6:6379
# 输出
>>> Performing Cluster Check (using node 10.211.55.6:6379)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Rebalancing across 3 nodes. Total weight = 3.00
Moving 5462 slots from 10.211.55.7:6379 to 10.211.55.6:6379
###########################################################
...
###########################################################
# 查看哈希槽节点分布情况
redis-cli -p 6379 cluster nodes
# 输出,可以看到 10.211.55.6:6379 的哈希槽范围现在是 [0-5461]
e667c40572bb7a34fae03ee407e0849728537497 10.211.55.8:6380@16380 master - 0 1681222393532 11 connected 10923-16383
a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379@16379 master - 0 1681222394571 13 connected 5462-10922
35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379@16379 slave e667c40572bb7a34fae03ee407e0849728537497 0 1681222394055 11 connected
d070ed606c2067bca37f7a079971eed5d67b8841 10.211.55.6:6380@16380 slave a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 0 1681222394256 13 connected
32f44e3aab49194db8e5e819a1dc851a86a1b584 10.211.55.7:6380@16380 slave 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 0 1681222393018 14 connected
5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379@16379 myself,master - 0 1681222393000 14 connected 0-5461
# 执行 check 命令,可以观察集群节点的健康状态
redis-cli --cluster check 10.211.55.6:6379
# 输出
10.211.55.6:6379 (5a54e2bb...) -> 1 keys | 5462 slots | 1 slaves.
10.211.55.8:6380 (e667c405...) -> 1 keys | 5461 slots | 1 slaves.
10.211.55.7:6379 (a1a7be53...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 2 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 10.211.55.6:6379)
M: 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad 10.211.55.6:6379
slots:[0-5461] (5462 slots) master
1 additional replica(s)
M: e667c40572bb7a34fae03ee407e0849728537497 10.211.55.8:6380
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
M: a1a7be53d2ebc9d54fb18c59f31171cf6d371f29 10.211.55.7:6379
slots:[5462-10922] (5461 slots) master
1 additional replica(s)
S: 35c17301dac6d01cdd06b01c449c895e1d5f6028 10.211.55.8:6379
slots: (0 slots) slave
replicates e667c40572bb7a34fae03ee407e0849728537497
S: d070ed606c2067bca37f7a079971eed5d67b8841 10.211.55.6:6380
slots: (0 slots) slave
replicates a1a7be53d2ebc9d54fb18c59f31171cf6d371f29
S: 32f44e3aab49194db8e5e819a1dc851a86a1b584 10.211.55.7:6380
slots: (0 slots) slave
replicates 5a54e2bb5a897956d2d1a08893be62c5fb0ed5ad
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
这里只提供相关命令,读者可以自行进行操作。
# 添加主节点
redis-cli --cluster add-node [new node ip]:[new node port] [one of master node ip]:[master node port]
# 添加从节点
redis-cli --cluster add-node [new node ip]:[new node port] [one of master node ip]:[master node port] --cluster-slave --cluster-master-id [masster-node-id]
# 重新分配哈希槽
# 可以执行自动分配
redis-cli --cluster rebalance --cluster-use-empty-masters [one of master node ip]:[master node port]
# 可以从指定的节点移动部分的哈希槽到新添加的节点
redis-cli --cluster reshard [one of master node ip]:[master node port] --cluster-from [from node id] --cluster-to [to node id] --cluster-slots [number of slots] --cluster-yes
删除一个节点,首先需要把改节点的哈希槽移除。
# 移除改节点的哈希槽
redis-cli --cluster reshard [one of master node ip]:[master node port] --cluster-from [from node id] --cluster-to [to node id] --cluster-slots [the number of slots] --cluster-yes
# 删除该节点
redis-cli --cluster del-node [one of master node ip]:[master node port] [node-id]