赞
踩
- 第一阶段:当slave和master建立连接后,会向master请求数据同步,master经过一些判断方式后知道了slave是第一次和我进行数据同步,就会将master当前的数据版本信息发送给slave,slave收到版本信息后进行保存。
- 第二阶段:master执行bgsave命令,创建一个子进程去执行RDB操作,将当前内存中的所有数据生成RDB文件,生成后将RDB文件发送给slave,slave拿到RDB文件后,就会清除本地数据,加载RDB文件当中的数据,但是,在子进程生成RDB文件以及发送RDB文件的过程当中,一旦master又有新的数据改动,此时master就会记录在这个RDB期间的所有命令-》存在一个repl_baklog文件中。
- 第三阶段:在记录完repl_baklog之后将这个文件再此发送给slave,slave拿到这个文件后,执行文件当中的命令,就可以保证与master当中数据一致,后续master只需要将最新生成的repl_baklog文件发送给slave,slave执行该命令即可。
有几个概念,可以作为判断依据:
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。
完整流程描述:
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
什么是增量同步?就是只更新slave与master存在差异的部分数据。
master如何知道slave与自己的数据差异在哪里?repl_baklog文件会记录master的数据改动,这个文件是一个固定大小的数组,且是环形数组。也就是说角标到达数组末尾后,会再此从0开始读写。这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
可以从以下几个方面来优化Redis主从就集群:
在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
什么意思呢?我们上述的全量同步,都是先生成RDB文件,RDB文件是需要经过磁盘IO的,生成后将该RDB文件发送给slave,磁盘IO是一项消耗非常大的操作,因此可以采用直接将数据写到网络当中,由网络直接发送,而不再进行磁盘的IO。
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
slave节点宕机恢复后可以找master节点同步数据,master节点宕机后怎么办?哨兵机制——》选举master的从节点作为主节点!
哨兵结构图:
监控:Sentinel 会不断检查您的master和slave是否按预期工作
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
3、新master选举
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的运行id大小,越小优先级越高。
当选出一个新的master后,该如何实现切换呢?
流程如下:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
在项目的启动类中,添加一个新的bean:
- @Bean
- public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
- return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
- }
这个bean中配置的就是读写策略,包括四种:
MASTER:从主节点读取
MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
REPLICA:从slave(replica)节点读取
REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master
围绕以下这张图展开描述:
所谓Redis集群,就是将0-16383个槽均分给集群当中的物理节点,所谓物理节点,就是我们之前所说的主节点,即对外提供写服务的节点,注意,此时的主节点可能不止一个。后续的客户端进行操作时,并非直接去操作某个节点,而是经过集群处理过后再去操作某个节点,这样一来,减少了单个节点的压力,类似于负载均衡的原理。
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
key中不包含“{}”,整个key都是有效部分
计算方式:利用CRC16算法对key进行计算得到一个hash值,然后对16384取余,得到的结果就是slot值。
Redis集群当中采用了CRC16算法,这个算法有如下特点:
①、对集群模式下的所有key进行CRC16计算,计算的结果始终在0-16383这个范围内。
②、对客户端的key进行CRC16计算时,同一个key多次进行计算,结果始终一致。
③、对客户端的不同key进行CRC16计算,计算的结果会出现不同的key的计算结果一致的情况。
基于以上的特点,因此我们可以得出结论:我们可使用的槽有16384个。
以图为例:
Node-1节点持有0~5000槽位
Node-2节点持有5001~8000槽位
Node-3节点持有8001~13000槽位
Node-4节点持有13001~16383槽位
问题1:说好的均分槽位呢?
此处所说的均分,并非是我们常规理解意义上的用16384除以节点个数,而是均匀的分配,使其槽位都分配出去。
问题2:节点个数有没有要求?
如果是16385个节点,那槽位不就没得分了?总得有一个节点没有节点分不到槽,此时,Redis集群就不会搭建成功。并且,我们我们为了保证集群的高可用性,建议每个主节点都至少持有一个从节点,当主节点宕机的时候,从节点可以顶替上来~
问题3:从节点如何顶替主节点?
主节点持有的槽位会转移给从节点,因为从节点上面的数据和主节点的一致,只需要将数据分配进槽位即可。
问题4:在原有集群当中添加新的节点?此时槽位如何分配?
在上图过程当中,我们比如添加Node-5节点,此时Node-5节点假设需要2000个槽位,此时其余四个节点就会拼凑出2000个槽位给Node-5节点,同时槽位当中的值也会转移给Node-5节点,这样一来,Node-5节点就算添加进了集群。
注意:需要保证0-16383个槽位都分布在某个节点上,不会存在槽位未分配的情况。
客户端操作步骤:
①、我们知道客户端操作Redis时,根据客户端持有的key-value数据,CRC16算法对key进行计算,根据计算得出的结果去找到持有这个槽位的节点。
②、找到节点之后,在节点上进行操作
③、集群维护节点-》节点维护槽-》槽维护值
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。