Redis系列之高可用集群模式介绍
主从模式,是redis集群最基本的模式,主库负责读写,从库负责读。主库的数据会同步到从库,但是从库写的数据不会自动同步到主库,除非用写脚本等方式手动同步。这种模式应急能力比较差,假如出现宕机的情况,需要手动进行修改
master
服务端收到slave的同步命令psync
后,判断slave传过来的master_replid
是否和master
的master_replid
一致,如果不一致或者传的是一个空的,就需要进行全量同步
master
开始执行bgsave
命令,生成一个RDB
文件,生成成功后,给到slave
,并且将master_replid
和offerset
传过去
slave
收到RDB
文件后,清空slave
自己内存中的数据,然后通过RDB
文件重新加载数据
补充细节:
Redis4.0
之前的版本,slave
给master
传的不是master_replid
,而是runid
,但是runid
每次实例重启都会改变,也就是会进行一次全量同步,所以Redis4.0
版本后采用replid
master
进行bgsave
生成RDB
文件时,因为bgsave
命令是异步的,所以在同步时候,可能会接收其它指令,所以这些指令会先暂存在一个内存空间replication_buffer
,等到slave
加载完RDB
文件后,再同步给slave
设置replication_buffer
大小可以通过如下进行配置
# 256mb:硬性限制,大于256M断开连接;64mb 60:软限制,超过64M并且超过了60s还没进行同步就会断开连接
client-output-buffer-limit replica 256mb 64mb 60
内存空间replication_buffer
不能设置太小,如果设置太小,超过了大小之后,为了数据安全,会关闭master
跟从库的连接,再次连接就得重新全量同步,但是问题还在,可能导致无限同步问题
有全量同步就会有增量同步的,那么redis什么时候会进行增量同步?增量同步的流程是怎么样的?
在全量同步时候说过,master
会给slave
一个偏移量offerset
,如果出现slave
跟master
网络断开一小会等情况,导致偏移量跟master
差了一点,网络恢复,slave
重新跟master
连接后,这时slave
会发送指令,判断是否需要增量同步还是全量同步?
master
会判断slave
传过来的master_replid
是否一致,如果一致,满足条件,进行下一步校验offerset
和积压缓存数据来判断,增量同步会去积压缓存replication_backlog_buffer
获取数据,如果偏移量只差了3条数据,同时在积压缓存里查找得到,就会进行增量同步补充细节:
master_replid
一致的情况,可能也不会进行增量同步,因为积压缓存的数据,可能是被覆盖了,导致需要增量同步的数据,查找不到,为什么会被覆盖?因为积压缓存是可以设置大小的,如果内存满了,就会覆盖之前的数据
设置积压缓存replication_backlog_buffer
的大小
repl-backlog-size 1mb
Redis
的主从模式是可以解决负载、数据备份等问题,但是,如果master
宕机的情况,slave
是不会自动升级为master
的,必须手动升级,所以就有了哨兵集群的方案,以及后面介绍的cluster集群
先看看官网对Sentinel
的介绍
大概意思是Redis Sentinel
在不适用Cluster集群的时候,为Redis提供了高可用性,并且提供了检测、通知、自动故障转移、配置提供等功能
监控 :能够监控我的redis实例是否正常运行
通知:如果redis
的实例出现问题,能够通知给其它实例以及其它Sentinel
自动故障转移:master
宕机,slave
可以自动升级为master
配置提供:Sentinel
可以提供Redis
的master实例地址,那么客户端只需要跟Sentinel
进行连接,master宕机了会提供新的master
总而言之,Sentinel
是独立于Redis服务的单独服务,并且它们之间是相互通信的,可以画图表示一下Redis
的哨兵模式:
下面说一下,master
宕机的情况,哨兵是怎么去监控的,并且怎么选举slave
作为新的master
的,首先,各Sentinel
之间,以及Sentinel
与master/slave
之间都会进行通信的,Redis故障转移的过程大致为:
Sentinel
是会一直和master
通信的,默认是1s发送ping
,当某个Sentinel
发现在一定时间内(down-after-milliseconds)没有收到master的有效回复,这个Sentinel
就会认为这个master
宕机了,这个时候还不会触发故障转移,只会标记一个SDOWN
(Subjectively Down condition)状态,也就是我们讲的主观下线sentinel
会去询问其它的sentinel
能否连上master
,如果超过法定人数quorum
都认为master
不可用,都标记SDOWN
状态,那么就会将master
标记为ODOWN
(Objectively Down condition)客观下线Sentinel
一般是集群的,所以需要选举一个Sentinel
来进行故障转移就好,并且这个Sentinel
在做故障转移的时候,其它Sentinel
不能进行故障转移Sentinel
,有两个判断因素 Quorum
如果小于等于sentinel
数量的一半,那么必须超过半数的sentinel授权,这个sentinel
才可以去做故障转移,不如有5台sentinel
,配置的quornum
为2,那么选举时候必须有3台以上的sentinel
授权Quorum
大于Sentinel
数量的一半,那么必须Quorum
的Sentinel
授权,故障迁移才能启动Sentinel
来做故障转移后,具体选举哪个Slave
来当master
,需要由各种因素综合考虑:
slave
与主服务器断开时间超过主服务器配置的超时时间(down-after-milliseconds)的十倍,被认为不适合成为master
,直接去除资格
replica-priority
,replica-priority
越小,优先级越高,但是配置为0的时候,永远没有资格升级为master
,具体参考https://redis.io/docs/manual/sentinel/#replica-selection-and-priorit
master
RunId
,可以通过info server
查看
Redis集群(分区)后,可能会有一致性的问题,也可以说是脑裂问题,其实就是有2个master,client会从不同的master写数据,从而在master恢复的时候,会导致数据丢失。
举个例子,一个集群的架构是有一个master
,下面还有两个slave
,假如出现master
和slave
连接不上,这时候就会选举,重新选举一个slave
作为master
,等到网络恢复后,原先的master
恢复连接,在这种集群架构里肯定不能有两个master
,所以会恢复原先的master
地位,新选举的master
变为slave
,显而易见这种情况会造成数据丢失了,因为故障过程,数据还可以继续写到原先的master
,一恢复网络,这个时间段写的数据都会丢失了,所以有什么方法可以避免这种情况?
首先对于这种问题,不能解决,只能避免,避免数据丢失的情况,在Redis官网给出了一种方案,需要在Redis.cfg
文件中加上配置:
# 至少有1个从节点同步到我主节点的数据,这样配置就可以避免原先断网的master进行数据写入,减少数据一致性问题
min-replicas-to-write 1
# 判断上面1个的延时时间必须小于等于10s
min-replicas-max-lag 10
redis
的哨兵模式提供了比如监控、自动故障转移等高可用方案,但是这种方案,容量相对固定,要进行持续扩容或者数据分片就不适合,所以有另外一种更复杂的集群方案,Cluster集群模式。
Cluster集群模式,Cluster模式支持多主多从,这种模式,按照key进行虚拟槽位分配,使得key分配到不同的主节点,使用这种模式使得集群节点有更大的容量,也可以持续进行扩容,如果主库节点出现宕机,也会从从库节点选出一个新的主库节点
所以,如果redis的数据量不是很大,就可以使用哨兵模式,如果Redis存储的数据量比较大,而且需要持续扩容,那么就需要选择Cluster模式
那么如何进行数据分片?数据分片相当于数据库的分表,不同的数据放到不同的表里。数据分片就是要把不同的数据放到不同的实例里面。
所以,在Redis里面提出一个Hash
槽,也可以称之为虚拟槽的概念。什么是虚拟槽?其实就是虚拟节点,Redis Cluster中有16384个虚拟槽。
key和虚拟槽怎么对应?会根据CRC16
取模16383得到一个0到16383的值,计算公式是:slot = CRC16(key) & 16383
,通过这个公式计算得到的值就表示key在哪个虚拟槽,举例:
set k1 1:
CRC16(k1) & 16383 = 10
set k2 1:
CRC16(k1) & 16383 = 5468
set k3 1:
CRC16(k1) & 16383 = 10988
# 所以k1、k2、k3对应的槽位分别是10、5468、10988
如果有3台主库,对应的槽位分别为
master1 0-5460虚拟槽
master2 5461-10922虚拟槽
master3 10923-16383虚拟槽
则,k1、k2、k3分别会放在master1、master2、master3
为什么使用16384个虚拟槽?
在Redis的官方issues也有给出答复,作者的亲自答复,请看原文:
翻译下:
看了大概意思是使用这个值是基于应用场景考虑,一般Cluster不太可能扩展到1000多个主节点,所以使用2k空间和16k的插槽比较合理,也不会占用太大空间,有读者还可能不太理解,所以继续分析,在redis中,cluster节点是会相互监测进行数据交互的,所以看下交互的数据头文件,cluster.h
typedef struct {
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 1. */
uint16_t port; /* TCP base port number. */
uint16_t type; /* Message type */
uint16_t count; /* Only used for some kind of messages. */
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
uint64_t configEpoch; /* The config epoch if it's a master, or the last
epoch advertised by its master if it is a
slave. */
uint64_t offset; /* Master replication offset if node is a master or
processed replication offset if node is a slave. */
char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
unsigned char myslots[CLUSTER_SLOTS/8];
char slaveof[CLUSTER_NAMELEN];
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
char notused1[34]; /* 34 bytes reserved for future usage. */
uint16_t cport; /* Sender TCP cluster bus port */
uint16_t flags; /* Sender node flags */
unsigned char state; /* Cluster state from the POV of the sender */
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
union clusterMsgData data;
} clusterMsg;
源码里有个char类型的变量unsigned char myslots[CLUSTER_SLOTS/8];
,表示的意思是为当前槽的数量除以8,因为一个char类型在c语言中大小被定义为1Byte,所以大小为16384/8/1024
=2kb,所以使用16384个虚拟槽的话,只需占用2kb的空间
但是为什么作者要举例65k?因为crc16
算法得到的hash
值是16bit,最大的值为65536,计算占用空间达到了8kb,所以数据传输会比较慢,所以一般场景16384个虚拟槽就符合需求,就取了一个16384的值,提升传输性能
补充知识点: