Redis系列之高可用集群模式介绍
1. Redis主从模式
1.1 什么是主从模式?
主从模式,是redis集群最基本的模式,主库负责读写,从库负责读。主库的数据会同步到从库,但是从库写的数据不会自动同步到主库,除非用写脚本等方式手动同步。这种模式应急能力比较差,假如出现宕机的情况,需要手动进行修改
1.2 全量同步
-
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
跟从库的连接,再次连接就得重新全量同步,但是问题还在,可能导致无限同步问题
1.3 增量同步
有全量同步就会有增量同步的,那么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
2. Redis哨兵模式
2.1 什么是哨兵模式?
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
的哨兵模式:
2.2 故障转移流程
下面说一下,master
宕机的情况,哨兵是怎么去监控的,并且怎么选举slave
作为新的master
的,首先,各Sentinel
之间,以及Sentinel
与master/slave
之间都会进行通信的,Redis故障转移的过程大致为:
-
发现master故障
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
不能进行故障转移 - 选举这个
Sentinel
,有两个判断因素- 如果
Quorum
如果小于等于sentinel
数量的一半,那么必须超过半数的sentinel授权,这个sentinel
才可以去做故障转移,不如有5台sentinel
,配置的quornum
为2,那么选举时候必须有3台以上的sentinel
授权 - 如果
Quorum
大于Sentinel
数量的一半,那么必须Quorum
的Sentinel
授权,故障迁移才能启动
- 如果
- 因为
-
选举slave转变为master
选出一个
Sentinel
来做故障转移后,具体选举哪个Slave
来当master
,需要由各种因素综合考虑:-
与master的断开连接时间
如果
slave
与主服务器断开时间超过主服务器配置的超时时间(down-after-milliseconds)的十倍,被认为不适合成为master
,直接去除资格 -
配置的优先级
配置
replica-priority
,replica-priority
越小,优先级越高,但是配置为0的时候,永远没有资格升级为master
,具体参考https://redis.io/docs/manual/sentinel/#replica-selection-and-priorit -
已复制的偏移量
数据最新的优先升级为
master
-
Run ID
每个实例启动都会有一个
RunId
,可以通过info server
查看
-
2.3 分区下的一致性
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
3. Reids Cluster模式
3.1 什么是Redis Cluster模式?
redis
的哨兵模式提供了比如监控、自动故障转移等高可用方案,但是这种方案,容量相对固定,要进行持续扩容或者数据分片就不适合,所以有另外一种更复杂的集群方案,Cluster集群模式。
Cluster集群模式,Cluster模式支持多主多从,这种模式,按照key进行虚拟槽位分配,使得key分配到不同的主节点,使用这种模式使得集群节点有更大的容量,也可以持续进行扩容,如果主库节点出现宕机,也会从从库节点选出一个新的主库节点
所以,如果redis的数据量不是很大,就可以使用哨兵模式,如果Redis存储的数据量比较大,而且需要持续扩容,那么就需要选择Cluster模式
3.2 Redis Cluster特点
- 多个节点之间按照key进行虚拟槽分配,也就是我们说的数据分片
- 当某些节点遇到故障的时候,其他节点还能继续服务
3.3 hash slot虚拟槽
那么如何进行数据分片?数据分片相当于数据库的分表,不同的数据放到不同的表里。数据分片就是要把不同的数据放到不同的实例里面。
所以,在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也有给出答复,作者的亲自答复,请看原文:
翻译下:
- 正常的心跳包携带节点的完整配置,可以用旧的方式以幂等的方式替换,以更新旧的配置,这意味着它们包含原始形式的节点插槽配置,该节点使用2k空间和16k插槽,但如果使用65k插槽会使用令人望而止步的8k空间
- 同时,由于其他设计权衡,Redis Cluster不太可能扩展到1000多个主节点
看了大概意思是使用这个值是基于应用场景考虑,一般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的值,提升传输性能
标签:同步,slave,Redis,集群,Sentinel,master,之高,节点 From: https://blog.csdn.net/u014427391/article/details/136744377补充知识点:
bit(位,又名“比特”):bit的缩写是b,是计算机中的最小数据单位(属于二进制的范畴,其实就是0或者1)
Byte(字节):Byte的缩写是B,是计算机文件大小的基本计算单位。比如一个字符就是1Byte,如果是汉字,则是2Byte。
1B(字节)=8b(位)
1 KB = 1024 B
1 MB = 1024 KB
1 GB = 1024 MB
1TB = 1024GB