Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。
数据分布
分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。
Redis Cluster采用哈希分区规则,常见的哈希分区规则有几种:
节点取余分区
使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式:hash(key)%N
计算出哈希值,用来决定数据映射到哪一个节点上。
存在问题:当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。
常用于数据库的分库分表规则,扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。
一致性哈希分区
先构造一个0~2^32
的哈希环,根据节点的名称(也可以是ip:port
)计算出hash值,根据其hash值将节点放置在hash环上。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的节点。
这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。
一致性哈希分区存在几个问题:
- 加减节点会造成哈希环中部分数据无法命中
- 当使用少量节点时,数据分布不均匀,节点变化将大范围影响哈希环中数据映射
虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如Redis Cluster槽范围是0~16383。槽是集群内数据管理和迁移的基本单位。
Redis数据分区
Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383
。每一个节点负责维护一部分槽以及槽所映射的键值数据。
集群功能限制
Redis集群相对单机在功能上存在一些限制,限制如下:
key批量操作支持有限。如
mset
、mget
,目前只支持具有相同slot值的key执行批量操作。对于映射为不同slot值的key由于执行mget
、mget
等操作可能存在于多个节点上因此不被支持。key事务操作支持有限。同理只支持多key在同一节点上的事务操作,当多个key分布在不同的节点上时无法使用事务功能。
key作为数据分区的最小粒度,因此不能将一个大的键值对象如
hash
、list
等映射到不同的节点。不支持多数据库空间。集群模式下只能使用一个数据库空间,即db0。
复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
搭建集群
准备节点
Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。每个节点需要开启配置cluster-enabled yes
,让Redis运行在集群模式下。
集群相关配置如下:
# 节点端口
port 6379
# 开启集群模式
cluster-enabled yes
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
# 集群内部配置文件
cluster-config-file "nodes-6379.conf"
启动所有节点:
[heql@ubuntu redis]$ redis-server conf/redis-6379.conf
[heql@ubuntu redis]$ redis-server conf/redis-6380.conf
[heql@ubuntu redis]$ redis-server conf/redis-6381.conf
[heql@ubuntu redis]$ redis-server conf/redis-6382.conf
[heql@ubuntu redis]$ redis-server conf/redis-6383.conf
[heql@ubuntu redis]$ redis-server conf/redis-6384.conf
6379节点启动成功,第一次启动时如果没有集群配置文件,它会自动创建nodes-6379.conf
的配置文件,文件名称采用cluster-config-file
参数项控制。如果启动时存在集群配置文件,节点会使用配置文件内容初始化集群信息。
如节点6379首次启动后生成集群配置如下:
[heql@ubuntu redis]$ cat data/nodes-6379.conf
f2526aea7802af433298257faf38788178aa2d9d :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
执行cluster nodes
命令获取集群节点状态:
127.0.0.1:6380> cluster nodes
abd6c61ec0b84ef066576bcacb605237c07d0606 127.0.0.1:6380 master - 0 1531192830080 1 connected
127.0.0.1:6380>
每个节点目前只能识别出自己的节点信息。现在启动6个节点,但每个节点彼此并不知道对方的存在,可以通过节点握手让6个节点彼此建立联系从而组成一个集群。
节点握手
执行cluster meet
命令让其他节点加入到集群中:
127.0.0.1:6379> cluster meet 127.0.0.1 6380
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6381
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6382
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6383
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6384
OK
127.0.0.1:6379>
在集群内任意节点上执行cluster meet
命令加入新节点,握手状态会通过消息在集群内传播,这样其他节点会自动发现新节点并发起握手流程。
使用cluster nodes
命令确认6个节点都彼此感知并组成集群:
127.0.0.1:6379> cluster nodes
8b5102ef796d9a334d3441941ad2ff13032bd68b 127.0.0.1:6382 master - 0 1531192833104 4 connected
abd6c61ec0b84ef066576bcacb605237c07d0606 127.0.0.1:6380 master - 0 1531192830080 1 connected
f2526aea7802af433298257faf38788178aa2d9d 127.0.0.1:6379 myself,master - 0 0 0 connected
58bc52e7f10b287b96a58a75f88b6f27aba06695 127.0.0.1:6381 master - 0 1531192832096 2 connected
e73d12ab613e74fc2abc5961c11120ca1f063ddd 127.0.0.1:6383 master - 0 1531192831088 3 connected
65dd8f6d8713882231a5897675345981dec02fd5 127.0.0.1:6384 master - 0 1531192834113 5 connected
节点建立握手之后集群还不能正常工作,这时集群处于下线状态,所有的数据读写都被禁止。通过如下命令可以看到:
127.0.0.1:6379> set hello world
(error) CLUSTERDOWN The cluster is down
通过cluster info
命令可以获取集群当前状态:
127.0.0.1:6379> 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:3
cluster_stats_messages_sent:482
cluster_stats_messages_received:482
被分配的槽cluster_slots_assigned
是0,由于目前所有的槽没有分配到节点,因此集群无法完成槽到节点的映射。只有当16384个槽全部分配给节点后,集群才进入在线状态。
分配槽
Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过cluster addslots
命令为节点分配槽。
[heql@ubuntu redis]$ redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0..5461}
OK
[heql@ubuntu redis]$ redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462..10922}
OK
[heql@ubuntu redis]$ redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923..16383}
OK
把16384个slot平均分配给6379、6380、6381三个节点。执行cluster info
查看集群状态:
127.0.0.1:6379> 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:2
cluster_current_epoch:5
cluster_my_epoch:3
cluster_stats_messages_sent:2294
cluster_stats_messages_received:2294
当前集群状态是OK,集群进入在线状态。所有的槽都已经分配给节点,执行cluster nodes
命令可以看到节点和槽的分配关系:
127.0.0.1:6379> cluster nodes
8b5102ef796d9a334d3441941ad2ff13032bd68b 127.0.0.1:6382 master - 0 1531192914889 4 connected
abd6c61ec0b84ef066576bcacb605237c07d0606 127.0.0.1:6380 master - 0 1531192916903 1 connected 5462-10922
f2526aea7802af433298257faf38788178aa2d9d 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
58bc52e7f10b287b96a58a75f88b6f27aba06695 127.0.0.1:6381 master - 0 1531192913881 2 connected 10923-16383
e73d12ab613e74fc2abc5961c11120ca1f063ddd 127.0.0.1:6383 master - 0 1531192915895 3 connected
65dd8f6d8713882231a5897675345981dec02fd5 127.0.0.1:6384 master - 0 1531192917910 5 connected
目前还有三个节点没有使用,作为一个完整的集群,每个负责处理槽的节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。使用cluster replicate
命令让一个节点成为从节点:
127.0.0.1:6382> cluster replicate f2526aea7802af433298257faf38788178aa2d9d
OK
127.0.0.1:6383> cluster replicate abd6c61ec0b84ef066576bcacb605237c07d0606
OK
127.0.0.1:6384> cluster replicate 58bc52e7f10b287b96a58a75f88b6f27aba06695
OK
通过cluster nodes
命令查看集群状态和复制关系,如下所示:
8b5102ef796d9a334d3441941ad2ff13032bd68b 127.0.0.1:6382 slave f2526aea7802af433298257faf38788178aa2d9d 0 1531193398779 4 connected
abd6c61ec0b84ef066576bcacb605237c07d0606 127.0.0.1:6380 master - 0 1531193399786 1 connected 5462-10922
f2526aea7802af433298257faf38788178aa2d9d 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
58bc52e7f10b287b96a58a75f88b6f27aba06695 127.0.0.1:6381 master - 0 1531193401806 2 connected 10923-16383
e73d12ab613e74fc2abc5961c11120ca1f063ddd 127.0.0.1:6383 slave abd6c61ec0b84ef066576bcacb605237c07d0606 0 1531193402814 3 connected
65dd8f6d8713882231a5897675345981dec02fd5 127.0.0.1:6384 slave 58bc52e7f10b287b96a58a75f88b6f27aba06695 0 1531193396758 5 connected
目前为止,依照Redis协议手动建立一个集群。它由6个节点构成,3个主节点负责处理槽和相关数据,3个从节点负责故障转移。
使用redis-trib.rb搭建集群
redis-trib.rb
是采用Ruby实现的Redis集群管理工具。内部通过Cluster相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用之前需要安装Ruby依赖环境。
安装Ruby环境
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
tar zxf ruby-2.3.1.tar.gz
cd ruby-2.3.1/
./configure && make
sudo make install
安装rubygem redis依赖:
wget http:// rubygems.org/downloads/redis-3.3.0.gem
sudo gem install -l redis-3.3.0.gem
在Redis源码编译后的目录,将生成的redis-trib.rb拷贝到可执行路径中:
sudo cp src/redis-trib.rb /usr/local/bin/
准备节点
配置和之前的一样,启动所有节点:
[heql@ubuntu redis]$ redis-server conf/redis-6379.conf
[heql@ubuntu redis]$ redis-server conf/redis-6380.conf
[heql@ubuntu redis]$ redis-server conf/redis-6381.conf
[heql@ubuntu redis]$ redis-server conf/redis-6382.conf
[heql@ubuntu redis]$ redis-server conf/redis-6383.conf
[heql@ubuntu redis]$ redis-server conf/redis-6384.conf
创建集群
启动好6个节点之后,使用redis-trib.rb create
命令完成节点握手和槽分配过程:
[heql@ubuntu redis]$ redis-trib.rb create --replicas 1 127.0.0.1:6379 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
--replicas
参数指定集群中每个主节点配备几个从节点,这里设置为1。
创建过程中首先会给出主从节点角色分配的计划,如下所示:
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:6379
127.0.0.1:6380
127.0.0.1:6381
Adding replica 127.0.0.1:6382 to 127.0.0.1:6379
Adding replica 127.0.0.1:6383 to 127.0.0.1:6380
Adding replica 127.0.0.1:6384 to 127.0.0.1:6381
M: 2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
M: a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
M: 6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
S: 4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382
replicates 2a98fe0eeab67575c51188b96894af97709c1aa1
S: 114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383
replicates a1e476e48b42db93e9b06d11527696e5824e6bab
S: c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384
replicates 6010817240f6337aeffd9c6976fc0445bcd38eda
Can I set the above configuration? (type 'yes' to accept):
当同意这份计划之后输入yes,redis-trib.rb开始执行节点握手和槽分配操作,输出如下:
>>> 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 127.0.0.1:6379)
M: 2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
M: a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
M: 6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
M: 4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382
slots: (0 slots) master
replicates 2a98fe0eeab67575c51188b96894af97709c1aa1
M: 114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383
slots: (0 slots) master
replicates a1e476e48b42db93e9b06d11527696e5824e6bab
M: c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384
slots: (0 slots) master
replicates 6010817240f6337aeffd9c6976fc0445bcd38eda
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
集群完整性检查
集群完整性指所有的槽都分配到存活的主节点上,只要16384个槽中有一个没有分配给节点则表示集群不完整。可以使用redis-trib.rb check
命令检测任意一个节点地址就可以完成整个集群的检查工作:
[heql@ubuntu redis]$ redis-trib.rb check 127.0.0.1:6379
提示集群所有的槽都已分配到节点:
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384
slots: (0 slots) slave
replicates 6010817240f6337aeffd9c6976fc0445bcd38eda
S: 4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382
slots: (0 slots) slave
replicates 2a98fe0eeab67575c51188b96894af97709c1aa1
S: 114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383
slots: (0 slots) slave
replicates a1e476e48b42db93e9b06d11527696e5824e6bab
M: a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380
slots:5461-10922 (5462 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.
扩容集群
准备新节点
[heql@ubuntu redis]$ redis-server conf/redis-6385.conf
[heql@ubuntu redis]$ redis-server conf/redis-6386.conf
加入集群
[heql@ubuntu redis]$ redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
[heql@ubuntu redis]$ redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379
迁移槽和数据
使用redis-trib.rb reshard
命令进行迁移:
[heql@ubuntu redis]$ redis-trib.rb reshard 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386
slots: (0 slots) master
0 additional replica(s)
M: 6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384
slots: (0 slots) slave
replicates 6010817240f6337aeffd9c6976fc0445bcd38eda
M: 3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385
slots: (0 slots) master
0 additional replica(s)
S: 4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382
slots: (0 slots) slave
replicates 2a98fe0eeab67575c51188b96894af97709c1aa1
S: 114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383
slots: (0 slots) slave
replicates a1e476e48b42db93e9b06d11527696e5824e6bab
M: a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380
slots:5461-10922 (5462 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.
How many slots do you want to move (from 1 to 16384)?
输入迁移的槽数量,这里为4096:
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID?
输入6385的节点ID作为目标节点,目标节点只能指定一个:
What is the receiving node ID? 3fd031f75c8902e379ba3c7918df0330199ad435
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:
这里分别输入节点6379、6380、6381三个节点ID,最后用done表示结束:
Type 'done' once you entered all the source nodes IDs.
Source node #1:2a98fe0eeab67575c51188b96894af97709c1aa1
Source node #2:a1e476e48b42db93e9b06d11527696e5824e6bab
Source node #3:6010817240f6337aeffd9c6976fc0445bcd38eda
Source node #4:done
数据迁移之前会打印出所有的槽从源节点到目标节点的计划,确认计划无误后输入yes执行迁移工作:
Moving slot 12286 from 6010817240f6337aeffd9c6976fc0445bcd38eda
Moving slot 12287 from 6010817240f6337aeffd9c6976fc0445bcd38eda
Do you want to proceed with the proposed reshard plan (yes/no)?
当所有的槽迁移完成后,reshard
命令自动退出,执行cluster nodes
命令检查节点和槽映射的变化,如下所示:
[heql@ubuntu redis]$ redis-cli -h 127.0.0.1 -p 6379 cluster nodes
7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386 master - 0 1531208776886 0 connected
6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381 master - 0 1531208773850 3 connected 12288-16383
c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384 slave 6010817240f6337aeffd9c6976fc0445bcd38eda 0 1531208775877 6 connected
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 master - 0 1531208774868 7 connected 0-1364 5461-6826 10923-12287
4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382 slave 2a98fe0eeab67575c51188b96894af97709c1aa1 0 1531208776382 4 connected
114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383 slave a1e476e48b42db93e9b06d11527696e5824e6bab 0 1531208770820 5 connected
a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380 master - 0 1531208773345 2 connected 6827-10922
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 1 connected 1365-5460
节点6385负责的槽变为:0-1364 5461-6826 10923-12287,由于槽用于hash运算本身顺序没有意义,因此无须强制要求节点负责槽的顺序性。
使用redis-trib.rb rebalance
命令检查节点之间槽的均衡性:
[heql@ubuntu redis]$ redis-trib.rb rebalance 127.0.0.1:6380
>>> Performing Cluster Check (using node 127.0.0.1:6380)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
*** No rebalancing needed! All nodes are within the 2.0% threshold.
可以看出迁移之后所有主节点负责的槽数量差异在2%以内,因此集群节点数据相对均匀,无需调整。
添加从节点
把节点6386作为6385的从节点,从而保证整个集群的高可用。
127.0.0.1:6386> cluster replicate 3fd031f75c8902e379ba3c7918df0330199ad435
OK
查看节点6386状态确认已经变成6385节点的从节点:
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 master - 0 1531209244430 7 connected 0-1364 5461-6826 10923-12287
7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386 myself,slave 3fd031f75c8902e379ba3c7918df0330199ad435 0 0 0 connected
到此整个集群扩容完成。
收缩集群
收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点。
首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。
例如把6381和6384节点下线,节点信息如下:
c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384 slave 6010817240f6337aeffd9c6976fc0445bcd38eda 0 1531209479038 3 connected
6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381 master - 0 1531209482096 3 connected 12288-16383
6381是主节点,负责槽(12288-16383),6384是它的从节点,下线6381之前需要把负责的槽迁移到其他节点。
迁移槽和数据
收缩正好和扩容迁移方向相反,6381变为源节点,其他主节点变为目标节点,源节点需要把自身负责的4096个槽均匀地迁移到其他主节点上。由于每次执行reshard命令只能有一个目标节点,因此需要执行3次reshard
命令,分别迁移1365、1365、1366个槽,如下所示:
[heql@ubuntu redis]$ redis-trib.rb reshard 127.0.0.1:6381
>>> Performing Cluster Check (using node 127.0.0.1:6381)
M: 6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381
slots:12288-16383 (4096 slots) master
1 additional replica(s)
M: a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380
slots:6827-10922 (4096 slots) master
1 additional replica(s)
M: 2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379
slots:1365-5460 (4096 slots) master
1 additional replica(s)
S: c65707ad29a25e5c349d49bde7021aa7fbab6536 127.0.0.1:6384
slots: (0 slots) slave
replicates 6010817240f6337aeffd9c6976fc0445bcd38eda
S: 7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386
slots: (0 slots) slave
replicates 3fd031f75c8902e379ba3c7918df0330199ad435
M: 3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385
slots:0-1364,5461-6826,10923-12287 (4096 slots) master
1 additional replica(s)
S: 4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382
slots: (0 slots) slave
replicates 2a98fe0eeab67575c51188b96894af97709c1aa1
S: 114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383
slots: (0 slots) slave
replicates a1e476e48b42db93e9b06d11527696e5824e6bab
[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)? 1365
What is the receiving node ID? 2a98fe0eeab67575c51188b96894af97709c1aa1
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:6010817240f6337aeffd9c6976fc0445bcd38eda
Source node #2:done
槽迁移完成后,6379节点接管了1365个槽12288~13652,如下所示:
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 8 connected 1365-5460 12288-13652
继续把1365个槽迁移到节点6380,完成后,6380节点接管了1365个槽13653~15017:
a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380 master - 0 1531210368640 9 connected 6827-10922 13653-15017
把最后的1366个槽迁移到节点6385中:
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 master - 0 1531210636485 10 connected 0-1364 5461-6826 10923-12287 15018-16383
到目前为止,节点6381所有的槽全部迁出完成,6381不再负责任何槽。状态如下所示:
6010817240f6337aeffd9c6976fc0445bcd38eda 127.0.0.1:6381 master - 0 1531210761005 3 connected
下线节点
当下线主节点具有从节点时需要把该从节点指向到其他主节点,因此对于主从节点都下线的情况,建议先下线从节点再下线主节点,防止不必要的全量复制。
6384节点下线操作,命令如下:
[heql@ubuntu redis]$ redis-trib.rb del-node 127.0.0.1:6379 c65707ad29a25e5c349d49bde7021aa7fbab6536
>>> Removing node c65707ad29a25e5c349d49bde7021aa7fbab6536 from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
6381节点下线操作,命令如下:
[heql@ubuntu redis]$ redis-trib.rb del-node 127.0.0.1:6379 6010817240f6337aeffd9c6976fc0445bcd38eda
>>> Removing node 6010817240f6337aeffd9c6976fc0445bcd38eda from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
节点下线后确认节点状态:
7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386 slave 3fd031f75c8902e379ba3c7918df0330199ad435 0 1531211262223 10 connected
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 master - 0 1531211260205 10 connected 0-1364 5461-6826 10923-12287 15018-16383
4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382 slave 2a98fe0eeab67575c51188b96894af97709c1aa1 0 1531211261215 8 connected
114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383 slave a1e476e48b42db93e9b06d11527696e5824e6bab 0 1531211259199 9 connected
a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380 master - 0 1531211255667 9 connected 6827-10922 13653-15017
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 8 connected 1365-5460 12288-13652
集群节点状态中已经不包含6384和6381节点,到目前为止,完成了节点的安全下线。
故障转移
当集群内少量节点出现故障时通过自动故障转移保证集群可以正常对外提供服务。
故障发现
主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。
故障恢复
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。
故障转移演练
确认集群状态:
7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386 slave 3fd031f75c8902e379ba3c7918df0330199ad435 0 1531212381329 10 connected
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 master - 0 1531212382340 10 connected 0-1364 5461-6826 10923-12287 15018-16383
4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382 slave 2a98fe0eeab67575c51188b96894af97709c1aa1 0 1531212384358 8 connected
114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383 slave a1e476e48b42db93e9b06d11527696e5824e6bab 0 1531212383349 9 connected
a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380 master - 0 1531212384863 9 connected 6827-10922 13653-15017
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 8 connected 1365-5460 12288-13652
强制关闭6385进程:
[heql@ubuntu redis]$ ps aux | grep redis-server | grep 6385
heql 3269 0.2 0.5 42320 11076 ? Ssl 15:28 0:13 redis-server *:6385 [cluster]
[heql@ubuntu redis]$ kill 3269
重新启动故障节点6385:
[heql@ubuntu redis]$ redis-server conf/redis-6385.conf
6385节点变为新主节点6386的从节点:
7b3384a04dd2ff984fe16b5620eb471034ad77d8 127.0.0.1:6386 master - 0 1531212837848 11 connected 0-1364 5461-6826 10923-12287 15018-16383
3fd031f75c8902e379ba3c7918df0330199ad435 127.0.0.1:6385 slave 7b3384a04dd2ff984fe16b5620eb471034ad77d8 0 1531212835826 11 connected
4ad9e89b058931c091cd943d1b2f79ce6d3e2fd6 127.0.0.1:6382 slave 2a98fe0eeab67575c51188b96894af97709c1aa1 0 1531212834818 8 connected
114e1c725459f88743711f680b1508bd7f691adc 127.0.0.1:6383 slave a1e476e48b42db93e9b06d11527696e5824e6bab 0 1531212839868 9 connected
a1e476e48b42db93e9b06d11527696e5824e6bab 127.0.0.1:6380 master - 0 1531212838858 9 connected 6827-10922 13653-15017
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 8 connected 1365-5460 12288-13652
请求重定向
在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED
重定向错误,通知客户端请求正确的节点。这个过程称为MOVED
重定向。
键key:test:1
对应槽5191正好位于6379节点负责的槽范围内,则set
命令执行成功。cluster keyslot
命令返回key所对应的槽:
127.0.0.1:6379> set key:test:1 value-1
OK
127.0.0.1:6379> cluster keyslot key:test:1
(integer) 5191
127.0.0.1:6379> cluster nodes
2a98fe0eeab67575c51188b96894af97709c1aa1 127.0.0.1:6379 myself,master - 0 0 8 connected 1365-5460 12288-13652
由于键key:test:2
对应槽是9252,不属于6379节点,则回复MOVED
重定向信息:
127.0.0.1:6379> set key:test:2 value-2
(error) MOVED 9252 127.0.0.1:6380
127.0.0.1:6379> cluster keyslot key:test:2
(integer) 9252
重定向信息包含了键所对应的槽以及负责该槽的节点地址,根据这些信息客户端就可以向正确的节点发起请求。在6380节点上成功执行之前的命令:
127.0.0.1:6380> set key:test:2 value-2
OK
其中键内部使用大括号包含的内容又叫做hash_tag
,它提供不同的键可以具备相同slot的功能,常用于Redis IO优化:
127.0.0.1:6380> cluster keyslot key:test:111
(integer) 10050
127.0.0.1:6380> cluster keyslot key:{test}:111
(integer) 6918
127.0.0.1:6380> cluster keyslot key:{test}:222
(integer) 6918
在集群模式下使用mget
等命令优化批量调用时,键列表必须具有相同的slot,否则会报错。这时可以利用hash_tag
让不同的键具有相同的slot达到优化的目的。
Smart客户端
每次执行键命令前都要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销。正因为如此通常集群客户端都采用另一种实现:Smart(智能)客户端。
大多数开发语言的Redis客户端都采用Smart客户端支持集群协议,Smart客户端通过在内部维护slot→node
的映射关系,本地就可实现键到节点的查找,从而保证IO效率的最大化,而MOVED
重定向负责协助Smart客户端更新slot→node
映射。