分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集
常见的分区规则有哈希分区和顺序分区两种,区别如下
分区方式
|
特点
|
代表产品
|
哈希分区
|
离散度好
数据分布和业务无关
无法顺序访问
|
Redis Cluster
Cassandra
Dynamo
|
顺序分区
|
离散度容易倾斜
数据分布和业务有关
可顺序访问
|
Bigtable
HBase
Hypertable
|
这里重点介绍哈希分区
Redis Cluster采用虚拟槽分区,把所有key按照哈希函数映射到0-16383整数槽内,计算公式slot=CRC16(key) & 16383 ,每个节点负责维护一部分槽和部分数据
Redis虚拟槽分区的特点
介绍完Redis集群分区规则后,开始手动搭建Redis集群,需要以下三个步骤
集群相关配置如下
# 端口
port 6379
# 开启集群模式
cluster-enabled yes
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
# 配置未见地址
cluster-config-file "nodes-6379.conf"
其他配置和单机模式下配置一致即可,配置文件命名规则redis-{port}.conf,准备好配置后启动所有节点
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf
redis-server conf/redis-6382.conf
redis-server conf/redis-6383.conf
redis-server conf/redis-6384.conf
查看节点日志是否正确,日志内容如下
cat log/redis-6379.log
* No cluster configuration found, I'm cfb28ef1deee4e0fa78da86abe5d24566744411e # Server started, Redis version 6.0.8
* The server is now ready to accept connections on port 6379
6379节点启动成功,第一次启动没有集群配置文件,它会自动创建一份,文件名使用cluster-config-file控制,如果启动时存在集群配置文件,节点使用配置文件初始化集群信息,当集群内节点信息发生变化,如添加节点,节点下线,故障转移等,节点会自动保存集群状态到配置文件中,Redis自动维护集群配置文件,不要手动修改,防止节点重启时产生集群信息错乱
如节点6379首次启动后生成集群配置如下
cat data/nodes-6379.conf
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected vars currentEpoch 0 lastVoteEpoch 0
文件内容记录集群初始状态,这里最重要的是节点ID,用于标识集群内的一个节点,之后很多集群操作都要通过节点ID完成,节点ID在集群初始化时只创建一次
节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信,达到感知对方的过程,节点握手是集群彼此通信的第一步,由客户端发起命令: cluster meet {ip} {port}
127.0.0.1:6379>cluster meet 127.0.0.1 6380
127.0.0.1:6379>cluster meet 127.0.0.1 6381
127.0.0.1:6379>cluster meet 127.0.0.1 6382
127.0.0.1:6379>cluster meet 127.0.0.1 6383
127.0.0.1:6379>cluster meet 127.0.0.1 6384
通过cluster nodes可以确认6个节点都彼此感知并且组成集群
127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 1468073975551 5 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 1468073978579 4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 1468073980598 3 connected
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468073974541 1 connected
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468073979589 2 connected
节点握手后集群还不能正常工作,这时集群处于下线状态,因为槽还没有被分配,只有当16384个槽全部分配给节点后,集群才进入在线状态,握手完成之后的状态如下
Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能相应和这些槽相关联的键命令。通过cluster addslots命令为节点分配槽。这里利用bash特性批量设置槽
redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923...16383}
把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:3
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_sent:4874
cluster_stats_messages_received:4726
执行cluster nodes查看节点和槽的分配关系
127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 1468076240123 5 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 1468076239622 4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 1468076240628 3 connected
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468076237606 579
1 connected 5462-10922
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468076238612 2 connected 10923-16383
目前还有三个节点没有使用,他们分别作为6379,6380,6381的从节点,保证当主节点出现故障时可以自动进行故障转移,使用cluster replicate {nodeId} 命令让一个节点成为从节点
127.0.0.1:6382>cluster replicate cfb28ef1deee4e0fa78da86abe5d24566744411e
127.0.0.1:6383>cluster replicate 8e41673d59c9568aa9d29fb174ce733345b3e8f1
127.0.0.1:6384>cluster replicate 40b8d09d44294d2e23c7c768efc8fcd153446746
OK
槽分配完成并且复制完成后,整个集群的结构如下
使用cluster nodes查看节点信息如下
127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave 40b8d09d44294d2e2 3c7c768efc8fcd153446746 0 1468076865939 5 connected cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 slave 8e41673d59c9568aa 9d29fb174ce733345b3e8f1 0 1468076868966 4 connected 40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 slave cfb28ef1deee4e0fa 78da86abe5d24566744411e 0 1468076869976 3 connected 8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468076870987 1 connected 5462-10922
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468076867957 2 connected 10923-16383
目前为止我们按照Redis协议手动建立了一个集群,由6个节点组成,3个主节点组分处理槽和相关数据,3个从节点负责负责故障转移,但是过程过于复杂,因此Redis官方提供了redis-cli 命令工具方便我们快速搭建集群
Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb 的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境。直接使用redis-cli的参数–cluster 来取代
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf
redis-server conf/redis-6382.conf
redis-server conf/redis-6383.conf
redis-server conf/redis-6384.conf
启动好6个节点之后,使用redis-cli --cluster create命令完成节点握手和槽分配过程,命令如下:
redis-cli --cluster create 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 --cluster-replicas 1
–cluster-replicas参数指定集群中每个主节点配备几个从节点,这里设置为1。我们出于测试目的使用本地IP地址127.0.0.1,如果部署节点使用不同的IP地 址,redis-cli会尽可能保证主从节点不分配在同一机器下,因此会重新排序节点列表顺序。节点列表顺序用于确定主从角色, 先主节点之后是从节点。创建过程中首先会给出主从节点角色分配的计划,如下所示。
>>> 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:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
Can I set the above configuration? (type 'yes' to accept): yes
当我们同意这份计划之后输入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 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
slots: (0 slots) slave
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
slots: (0 slots) slave
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
slots: (0 slots) slave
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
最后的输出报告说明:16384个槽全部被分配,集群创建成功。这里需要注意给redis-cli的节点地址必须是不包含任何槽/数据的节点,否则会拒绝创建集群
cluster nodes查看节点信息
127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602248698000 3 connected 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602248699825 3 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602248696000 1 connected 0-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602248697733 1 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602248699000 2 connected 5461-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602248697000 2 connected
在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责那些数据,是否出现故障等状态信息。常见的元数据维护方式分为两种:集中式和P2P方式。Redis采用P2P的Gossip协议,Gossip的工作原理就是节点彼此不断通信交换信息,一段时间后所有节点都会知道集群完整信息,类似流言的方式
常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等
Redis集群内节点通信采用固定频率(每秒十次),因此选择需要通信的节点列表显得尤为重要,通信节点选择过多虽然可以及时交换信息,但是会加重带宽和计算的消耗,选择过少会影响节点之间交换信息的速率,从而影响故障发现,新节点发现等需求的速度,因为Gossip需要兼顾信息交换实时性和成本开销
Redis集群内节点维护定时任务默认每秒十次,每秒会随机选出五个节点,找到最久没有通信的节点发送ping消息,用户保证Gossip的随机性。每100毫秒会扫描本地节点列表,如果发现节点最近一次接收pong消息的时间大于cluster-node-time/2, 则立刻发送ping消息,防止该节点信息太长时间未更新。根据以上规则,每个节点每秒需要发送ping消息的数据为1+10 *num(node.pong_received > cluster-node-time/2), 因此cluster-node-time的配置非常重要
Redis集群提供了灵活的节点扩容和收缩方案,在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容,如下
Redis集群可以实现对节点的灵活上下限控制,其中原理可以抽象为槽和对应数据在不同节点之间灵活移动,首先来看之前搭建的集群槽和数据与节点的对应关系
三个主节点分别维护自己负责的槽和对应数据,如果希望加入一个节点实现集群扩容,需要通过相关命令把一部分槽和数据迁移给新节点,如下
每个节点把一部分槽和数据迁移到新的节点6385,每个节点负责的槽和数据比之前少了从而达到了集群口容的目的
手动扩容集群分为以下三个步骤, 手动扩容集群只简单讲述步骤
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf
127.0.0.1:6379> cluster meet 127.0.0.1 6385
127.0.0.1:6379> cluster meet 127.0.0.1 6386
加入集群之后可以通过cluster nodes查看新加入的节点信息,此时节点结构为
redis-cli工具也实现了为现有集群添加新节点的命令
$ redis-cli --cluster add-node 127.0.0.1:6385 127.0.0.1:6379
>>> Adding node 127.0.0.1:6385 to cluster 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
slots: (0 slots) slave
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
slots: (0 slots) slave
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
slots: (0 slots) slave
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:6385 to make it join the cluster.
[OK] New node added correctly.
$ redis-cli --cluster add-node 127.0.0.1:6386 127.0.0.1:6379
>>> Adding node 127.0.0.1:6386 to cluster 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
slots: (0 slots) slave
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
slots: (0 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
slots: (0 slots) slave
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
slots: (0 slots) slave
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:6386 to make it join the cluster.
[OK] New node added correctly
正式环境建议使用redis-cli --cluster add-node 命令加入新节点,该命令内部会执行新节点状态检查,如果新节点已经加入其他集群或者包含数据,则放弃加入集群打印信息
(1)槽迁移计划,槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的那些槽需要迁移到新节点,如下,原来集群有三个主节点,6379负责5461个槽,6381,6382分别负责5460个槽,现在扩容多了一个主节点,每个节点需要负责4096个槽,那么6379节点需要迁移1365个槽到6385,相应的数据也要对应迁移,另外两个节点也类似
(2) 迁移数据
手动迁移流程说明
手动执行命令演示槽迁移过程,是为了更好理解迁移流程,实际操作时要涉及大量槽和非常多的key,redis-cli提供了槽迁移重分片功能,可以内部完成这些事情
命令如下:
$ redis-cli --cluster reshard 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
slots: (0 slots) slave
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
M: 90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386
slots: (0 slots) master
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
slots: (0 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
slots: (0 slots) slave
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
slots: (0 slots) slave
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[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
打印集群每个节点信息后,需要确认迁移的槽数量,这里数据4096个
How many slots do you want to move (from 1 to 16384)? 4096
输入6385的节点ID作为目标节点,目标节点只能有一个
What is the receiving node ID? ece23a03616beb2f133ef45a93d20966de0d6e78
之后输入源节点的ID,这里分别输入节点6379,6380,6381三个节点ID,最后用done表示结束
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: 5ae57a7bf02314899758adb2cc60efe04a4a2caa
Source node #2: 952cb6be80998a86c95d273b04031de1fd5afffa
Source node #3: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
Source node #4: done
槽迁移完成后,reshard命令自动推出,执行cluster nodes可以查看节点和槽映射的变化
127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602313309000 3 connected 12288-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602313308000 3 connected
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 master - 0 1602313309945 7 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602313310000 1 connected 1365-5460
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602313310000 8 connected 0-1364 5461-6826 10923-12287
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602313309000 1 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602313312025 2 connected 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602313310985 2 connected
槽6385负责的槽变为0-1364 5461-6826 10923-12287。由于槽用于hash运算本身顺序没有什么意义,因此无需强制要求节点负责槽的顺序性,迁移之后建议使用redis-cli --cluster rebalance命令检查节点之间槽的均衡性,命令如下
$ redis-cli --cluster rebalance 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
[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.00% threshold.
最后添加从节点,把6386节点添加为6385的从节点,此时整个集群扩容完成
127.0.0.1:6386> cluster replicate ece23a03616beb2f133ef45a93d20966de0d6e78
127.0.0.1:6386> cluster nodes
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602313782997 3 connected
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602313782000 3 connected 12288-16383
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602313786117 2 connected 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602313783000 2 connected
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602313784037 8 connected 0-1364 5461-6826 10923-12287
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 myself,slave ece23a03616beb2f133ef45a93d20966de0d6e78 0 1602313783000 8 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 master - 0 1602313783000 1 connected 1365-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602313785077 1 connected
收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点,安全下线节点流程如下
流程说明
这里下线6385,6385是上一节刚刚加入的主节点,6386是它的从节点,下线6385之前,需要把它所负责的槽迁移到其他节点
如下所示, 总共需要执行三次reshard,分别迁移1365,1365,1366个槽到6379,6380,6381节点中去,这里采用某次的reshard作为示例
$ redis-cli --cluster reshard 127.0.0.1:6385
>>> Performing Cluster Check (using node 127.0.0.1:6385)
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
slots: (0 slots) slave
replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
slots:[12288-16383] (4096 slots) master
1 additional replica(s)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
slots:[1365-5460] (4096 slots) master
1 additional replica(s)
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
slots:[6827-10922] (4096 slots) master
1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
slots: (0 slots) slave
replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386
slots: (0 slots) slave
replicates ece23a03616beb2f133ef45a93d20966de0d6e78
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
slots: (0 slots) slave
replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
[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? 5ae57a7bf02314899758adb2cc60efe04a4a2caa
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: ece23a03616beb2f133ef45a93d20966de0d6e78
Source node #2: done
迁移完成后执行cluster nodes可以发现6385不再持有槽
cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602318334277 11 connected 6826 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318332201 11 connected
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318333000 11 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602318332000 9 connected 0-5460
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602318333241 8 connected
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602318332000 9 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602318331000 10 connected 5461-6825 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602318332000 10 connected
4.2.2 忘记节点
Redis提供了cluster forget {downNodeId}实现该功能,但是需要在所有主节点执行此命令,当节点收到命令后,会把nodeId指定的节点加入禁用列表中,禁用列表内的节点不再发送Gossip消息,禁用列表有效期是60s,超过60s会再次参与消息交换,也就是说当第一次forget命令发出后,我们有60s时间让集群内所有节点忘记下线节点,线上不建议直接使用cluster forget命令,建议使用redis-cli --cluster del-node {host:port}{downNodeId}命令
~# redis-cli --cluster del-node 127.0.0.1:6379 ece23a03616beb2f133ef45a93d20966de0d6e78
>>> Removing node ece23a03616beb2f133ef45a93d20966de0d6e78 from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
~# redis-cli --cluster del-node 127.0.0.1:6379 90be5b0642169f6b0f7ca7d1afcb569176923713
>>> Removing node 90be5b0642169f6b0f7ca7d1afcb569176923713 from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
执行cluster nodes可以看到6385 6386已经从集群中移除
127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602318717000 11 connected 6826 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318717921 11 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602318718000 9 connected 0-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602318718961 9 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602318717000 10 connected 5461-6825 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602318715000 10 connected
Redis集群自身实现了高可用。高可用首先需要解决集群部分失败的场景: 当集群中少量节点出现故障时通过自动故障转移保证集群可以正常对外提供服务。本届介绍故障转移的细节
集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为相应,如果在cluster-node-timeout时间内一直通信失败,则发送节点认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态
当某个节点判断另外一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。ping/pong消息的消息体会携带1/10的其他节点状态数据,当接收节点发现消息体中含有主观下线的节点状态时,会保存到本地下线报告链表中
通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点时主观下线时,触发客观下线流程,这里有两个问题
如果在cluster-node-time*2时间内无法收集到一半以上槽节点的下线报告,那么之前的下线报告将会过期,也就是说主观下线上报的速度追赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下线从而导致故障转移失败。因此不建议将cluster-node-time设置得过小
这里针对下线报告和尝试客观下线逻辑进行详细说明
(1) 维护下线报告列表
每个节点都存在一个下线链表结构,保存了其他主节点针对某节点的下线报告,下线报告中保存了报告故障的节点结构和最近收到的下线报告的时间,当接收到fail状态时,会维护对应节点的下线上报链表
每个下线报告都存在有效期,每次在尝试客观下线时,都会检测下线报告是否过期,对于过期的下线报告将被删除,如果在cluster-node-time*2 的时间内该下线报告没有得到更新则过期并删除
下线报告的有效期限是server.cluster_node_timeout*2,主要是针对故障 误报的情况。例如节点A在上一小时报告节点B主观下线,但是之后又恢复正常。现在又有其他节点上报节点B主观下线,根据实际情况之前的属于误
报不能被使用
(2)尝试客观下线
集群中的节点每次接收到其他节点的pfail状态,都会尝试促发客观下线
流程如下
流程说明:
1)首先统计有效的下线报告数量,如果小于集群内持有槽的主节点总数的一半则退出。
2)当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
3)向集群广播一条fail消息,通知所有的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID。
故障恢复流程如下
127.0.0.1:6379> cluster info
... cluster_current_epoch:15 // 整个集群最大配置纪元
cluster_my_epoch:13 // 当前主节点配置纪元
(2)广播选举消息
在集群内广播选举消息(FAILOVER_AUTH_REQUEST),并记录已发送过消息的状态,保证该从节点在一个配置纪元内只能发起一次选举。消息内容如同ping消息只是将type类型变为FAILOVER_AUTH_REQUEST
5.3 故障转移时间
根据以上分析可以预估故障转移时间
failover-time(毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000
为了保证集群完整性,默认情况下当集群16384个槽任何一个没有指派到节点时整个集群不可用。执行任何键命令返回(error)CLUSTERDOWN Hash slot not served错误。这是对集群完整性的一种保护措施,保证所有的槽都指派给在线的节点。但是当持有槽的主节点下线时,从故障发现到自动完成转移期间整个集群是不可用状态,对于大多数业务无法容忍这种情况,因此建议将参数cluster-require-full-coverage配置为no,当主节点故障时只影响它负责槽的相关命令执行,不会影响其他主节点的可用性
cluster-node-timeout 应根据线上环境合理配置,配置过小容易造成带宽消耗过大,另外集群节点数目过多,节点间通信也更加频繁,一般要注意三个点
集群模式下内部实现对所有的publish命令都会向所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一次,加重带宽负担
当频繁应用
Pub/Sub功能时应该避免在大量节点的集群内使用,否则会严重消耗集群内网络带宽。针对这种情况建议使用sentinel结构专门用于Pub/Sub功能,从而规避这一问题
集群倾斜指不同节点之间数据量和请求量出现明显差异,这种情况将加大负载均衡和开发运维的难度
主要分为以下几种:
集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均
避免方式如下:
1)合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取。
2)不要使用热键作为hash_tag,避免映射到同一槽。
3)对于一致性要求不高的场景,客户端可使用本地缓存减少热键调用
可以使用readonly命令打开客户端连接只读状态,当开启只读状态时,从节点接收读命令处理流程变为:如果对应的槽属于自己正在复制的主节点则直接执行读命令,如果没有开启readonly,那么从节点接收到读请求将会重定向到主节点令,否则返回重定向信息
6.5.2 读写分离
集群模式下的读写分离,同样会遇到:复制延迟,读取过期数据,从节点故障等问题。针对从节点故障问题,客户
端需要维护可用节点列表,集群提供了cluster slaves{nodeId}命令,返回nodeId对应主节点下所有从节点信息
参考文献
Redis开发与运维
Redis 5.0 redis-cli --cluster help()
加大负载均衡和开发运维的难度
主要分为以下几种:
集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均
避免方式如下:
1)合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取。
2)不要使用热键作为hash_tag,避免映射到同一槽。
3)对于一致性要求不高的场景,客户端可使用本地缓存减少热键调用
可以使用readonly命令打开客户端连接只读状态,当开启只读状态时,从节点接收读命令处理流程变为:如果对应的槽属于自己正在复制的主节点则直接执行读命令,如果没有开启readonly,那么从节点接收到读请求将会重定向到主节点令,否则返回重定向信息
6.5.2 读写分离
集群模式下的读写分离,同样会遇到:复制延迟,读取过期数据,从节点故障等问题。针对从节点故障问题,客户
端需要维护可用节点列表,集群提供了cluster slaves{nodeId}命令,返回nodeId对应主节点下所有从节点信息
参考文献
Redis开发与运维
Redis 5.0 redis-cli --cluster help()