redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用等方面变现一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置的过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率。
redis3.0以后推出了高可用集群模式redis cluster。
redis cluster集群是一个由多个主从节点集群组成的分布式服务集群,它具有复制、高可用和分片特性。cluster集群不需要sentinel 哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,根据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。cluster集群的性能和高可用性均优于之前的哨兵模式,且配置非常简单。
redis集群需要至少三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个slave节点,总共6个redis节点,搭建集群步骤如下:
三个主节点:6382、6383、6384
三个从节点:6385、6386、6387
注意开放端口6382、6383、6384、6385、6386、6387以及节点间gossip通信端口16382、16383、16384、16385、16386、16387
step1:在redis-5.0.3文件夹下新建文件夹cluster,在cluster中新建6382、6383、6384、6385、6386、6387六个文件夹
将上篇文章中用到的redis.conf配置文件copy到6382下,修改下面内容:
#如需设置密码需要增加如下配置:
step2:将6382下的redis6382.conf复制到6383、6384文件夹下,修改相关配置
step3:将6382下的redis6382.conf复制到6385、6386、6387三台从机文件夹下,修改同上
step4:分别启动6个redis实例,然后检查是否启动成功
step5:用redis-cli创建整个redis集群(redis5之前版本集群是依靠ruby脚本redis-trib.rb实现的)
src/redis-cli -a 123456 --cluster create --cluster-replicas 1 116.62.71.39:6382 116.62.71.39:6383 116.62.71.39:6384 116.62.71.39:6385 116.62.71.39:6386 116.62.71.39:6387
注意: --cluster create --cluster-replicas 1 这里面的1表示为每个主服务器创建一个从服务器
可以看到6382、6383、6384是主机,其余三个是从机
step6:验证集群
连接任意一个客户端:src/redis-cli -a -c -h -p (-a访问服务端密码,-c表示集群,指定ip和端口)
例如:src/redis-cli -a 123456 -c -h ip -p 6382
查看集群信息:cluster info
查看节点列表:cluster nodes 可以看到主从关系
set数据验证:
注意:关闭集群需要逐个redis进行关闭
例如:src/redis‐cli ‐a 123456 ‐c ‐h 192.168.0.60 ‐p 6382 shutdown
src/redis‐cli ‐a 123456 ‐c ‐h 192.168.0.60 ‐p 6383 shutdown
src/redis‐cli ‐a 123456 ‐c ‐h 192.168.0.60 ‐p 6384 shutdown 等等逐个关闭
redis cluster将所有数据划分为16384个 slots(槽位),每个节点负责其中一部分槽位。槽位信息存储于每个节点中。
当redis cluster的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存到客户端本地。这样当客户端要查找某个key时,可以直接定位到目标节点,同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
槽位定位算法:cluster 默认会对key值使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位 HASH_SLOT=CRC16(key) mod 16384
可以看到对key为name1进行计算出来的结果是相同的
跳转重定位:当客户端向一个错误的节点发出了指令,该节点会发现指令的key所在的槽位并不归自己管,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连接这个节点获取数据。客户端收到指令后除了跳转正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有key将使用新的槽位映射表。
Redis集群节点间的通信机制:redis cluster节点间采取gossip协议进行通信
gossip通信的10000端口:redis cluster每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如6379,那么用于节点间通信的就是16379端口。每个节点每隔一段时间就会往另外几个节点发送ping消息,同时其他节点收到ping消息后返回pong信息。
gossip协议包含多种消息,包括ping、pong、meet、fail等等
ping:每个节点都会频繁给其他节点发送ping,其中包括自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等)
pong:对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新
meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信。
fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点有节点宕机了
网络抖动:为了解决网络抖动导致的集群节点之间突然不可访问,然后很快又恢复正常的情况,cluster提供了一个配置 cluster-node-timeout ,表示当某个节点持续timeout时间失联时才认定该节点故障然后进行选举新的主节点。
redis cluster集群选举原理:当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master ,由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,过程如下:
1、salve发现自己的master变为了FAIL
2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
4、尝试failover的salve收集master返回的FAILOVER_AUTH_ACK
5、salve收到超半数master的ack后变成新的master(这也是集群需要至少三个主节点原因)
6、salve成为master后广播消息通知其他节点
注意:从节点并不是在主节点一进入FAIL状态就马上尝试进行选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,salve如果立即尝试选举的话其他master或许尚未意识到FAIL状态,可能拒绝投票。
延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方 式下,持有最新数据的slave将会首先发起选举(理论上)。
集群脑裂数据丢失问题:redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。
规避方法可以在redis配置里加上参数 min‐replicas‐to‐write 1 表示写数据成功最少同步的slave的数量,当然这个配置会一定程度上影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要根据场景权衡。
集群是否完整才能对外提供服务:当redis.conf配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
redis集群为什么至少需要三个master节点,并且推荐节点数为奇数:因为新的master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
redis集群对批量操作命令的支持:
上图可以看到对于mset批量操作是会报错的。这是因为对于类似mset、mget这样操作多个key的原生批量命令,redis集群只支持所有的key落在同一个slot的情况,由于上图三个key计算出来的slot肯定不是同一个,因此redis会进行报错。
当然如果有多个key一定要用mset命令在集群上操作,可以在key前面加上{XX},这样参数数据分片hash计算的只会是大括号里面的值,这样能确保不同的key能落在同一个slot内,
例如:mset {name}:has hanansheng {name}:zs zhangsan
查看集群状态可以知道现在三台主机是6382、6383、6384;6382端口的实例节点存储0-5460这些hash槽,6383存储的是5461-10922范围的槽位,6382存储10923-16383的槽位,slave点是每个主节点的备份从节点,不显示存储槽位。
我们在原集群基础上再增加一主(7000)一从(7001)
要开放端口7000、7001以及gossip通信接口17000、17001
step1:在原来cluster目录下新建7000和7001文件夹
step2:拷贝原有的redis.conf到7000和7001目录下
step3:修改7000和7001目录下的配置文件内容,修改点如下:
#如需设置密码需要增加如下配置:
step4:启动7000和7001两个服务并查看服务状态
step5:配置7000为主节点加入集群
注意点1:确保7000redis中无其他数据,否则会失败
注意点2:保证7000端口已经开放【包括gossip通信端口17000也已开放】,否则会超时,不会看到【ok】New node added correctly
使用add-node命令新增一个主节点7000(master),下面命令前面的ip:端口为新增节点,后面的ip:端口为集群中已存在(主从都可)的节点(相当于入党介绍人)
src/redis-cli -a 123456 --cluster add-node 116.62.71.39:7000 116.62.71.39:6382
可以看到节点就多了7000的master:
step6:当添加节点成功后,新增的节点不会有任何数据,因为它还没有分配任何slot,需要手动为新节点分配槽位
使用redis-cli命令为7000分配hash槽,找到集群中任意一个主节点,对其进行重新分片,我就找集群中的6382进行操作
src/redis-cli -a 123456 --cluster reshard 116.62.71.39:6382
step6:查看集群的新状态:可以看到7000的槽位是其他三个master贡献出来的
到此为止7000加入集群成功,并且是master主节点!!!
然后给7000主节点添加从节点slave7001:
step1:将7001加入集群(第一个ip为添加的节点,第二个为入群介绍人)
src/redis-cli -a 123456 --cluster add-node 116.62.71.39 7001 116.62.71.39:6382
如图所示:还是一个master节点,没有被分配任何hash槽
接下来我们需要执行replicate命令来指定当前节点(7001从节点)的主节点id是哪个,首先需要连接新加的7001节点客户端,然后使用集群命令进行操作,把当前节点指定到一个主节点下
然后再查看集群状态:可以看到7001已经是7000的slave了
这个时候集群扩容完成,增加了一主一从!!!
服务缩容,由于访问量减少了,用不了四主四从这么多机器了,因此打算去掉一主(7000)一从(7001),下面开始操作
step1:删除从节点7001(都是先删除从节点在删除主节点)
用del-node删除从节点7001,指定删除节点ip和端口以及节点id
src/redis-cli -a 123456 --cluster del-node 116.62.71.39:7001 节点id
再次查看集群状态可以看到7001已经没有了
step1:删除主节点7000主节点,主节点删除相对麻烦一些,因为主节点里面分配了hash槽,所以必须先把7000里的hash槽释放到其他主节点,然后再进行移除节点操作,不然会出现数据丢失问题(目前只能把master数据迁移到一个节点上,暂时做不了平均分配)
src/redis-cli -a 123456 --cluster reshard 116.62.71.39:7000
再看集群状态,可以看到7000已经没有了槽位,槽位迁移到了6372
最后直接使用del-node命令删除7000主节点即可:
src/redis-cli -a 123456 --cluster del-node 116.62.71.39:7000 节点id
再查看集群状态,发现7000节点也没有了,恢复到原来三主三从状态
扩容与缩容全部完成!!!!!!