目前已更新系列:
当前:Redis高级----主从、哨兵、分片的原理
计算机网络--面试总结四(HTTP、RPC、WebSocket、SSE)-CSDN博客
知识积累之ThreadLocal---InheritableThreadLocal总结
本次Redis的总结是基于黑马的redis课程视频进行总结的,主要是梳理逻辑以及总结面试的时候能按照梳理的顺序进行作答不卡顿,大家向详细学习,可以去bilibili学习
主从同步的原理
全量同步
完整流程描述:
- slave节点请求增量同步
- master节点判断该请求的replid,发现不一致,说明是第一次进行主从同步拒绝增量同步
- master将执行bgsave命令将完整内存数据生成RDB,发送RDB到slave
- slave清空本地数据,加载master的RDB
- master将RDB期间的客服端发送过来的新命令记录在repl_baklog,并持续将log中的命令发送给slave
- slave执行接收到的命令,保持与master之间的同步
这里有一个问题,master如何得知salve是第一次来连接呢??---》replid相同即代表不是第一次连接,因为如果是第一次连接主节点会将自己的replid发送给从节点然后从节点就将其设置为自己的replid
有几个概念,可以作为判断依据:
- Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
- offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
增量同步
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
slave重启之后会执行增量同步,因为slave由于宕机那么master节点中记录这宕机期间所有的新命令到repi_baklog中,然后发送给刚恢复的该从节点,那么该从节点就开始做同步工作了
什么是增量同步?就是只更新slave与master存在差异的部分数据。如图:
由于不是第一次连接,那么此时master节点并不会将repl_baklog中的命令全部发给slave节点而是,将从节点发来的offset的位置开始读操作,然后就可以将slave错过的消息重新加载
流程:
- slave节点重启后会向主节点发送请求,请求中会带上replid和当前节点所更新到的位置即offset偏移量
- 主节点收到请求后对从节点发过来的replid进行检查,发现和自己的replid一样,说明不是第一次做主从同步,那么主节点会向从节点发送continue给从节点,高数从节点不用做RDB全量同步
- 接着主节点会取缓冲文件repl_log中对比当前自己的最新偏移量和slave发送过来的偏移量,然后就开始发送从节点到主节点的偏移量区间的命令,相当于从节点就开始一直追着主节点的offset走,最终达到同步的效果
- 但是当slave节点宕机的时间太长,此时主节点中写的新命令覆盖了原来从节点的offset位置,那么此时就只有做全局同步操作,才能确保主从一致了
主从同步的优化
可以从两个方面进行考虑
- 提高全量同步的性能
- 减少全量同步(优先考虑)
可以从以下几个方面来优化Redis主从就集群:
- 提高全量同步的性能
-
- 在master中配置repl-diskless-sync yes启用无磁盘复制即当我们要开始写RDB文件时,不把它写在磁盘中,而是直接写在网络中,这样就能避免全量同步时的磁盘IO。
-
-
- 将master中的redis.conf配置文件的repl-diskless-sync yes 改为yes表示,当我们在写RDB时我们不把他写道磁盘中,而是通过网络直接发给slave,使用场景,
- 使用场景:磁盘读写比较慢,网络带宽良好,
-
-
- Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
- 减少全量同步:
-
- 适当提高repl_baklog的大小,因为触发全量同步的另外一个因素就是,主节点写的新命令覆盖了slave还未同步命令,此时就会做全量同步,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
小结:
全量同步和增量同步的区别
- 全量同步:master会将内存中的数据生成RDB,发送RDB到slave,后续的命令则记录在repl_backlog中,然后逐个发送给slave
- 增量同步:slave节点提交自己的offset给master,master收到之后,会从repli_backlog中将从节点偏移量offset之后的命令发送给slave
什么时候执行全量同步
- slave节点第一次连接master节点时
- slave节点断开时间太久,repl_backlog的offset已经被覆盖时
什么时候执行增量同步
- slave节点断开后又恢复且再主节点的repl_backlog中能找到从节点的offset时
哨兵的结构如图:
哨兵模式
哨兵的作用如下:
哨兵同时也是一个集群,因为如果哨兵挂了,那么不就不能监控了
- 监控:Sentinel 会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主,即故障节点恢复后就变成从节点了
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
集群监控原理
Sentinel是基于心跳机制监测服务状态,会每隔1秒向集群的每个实例发送ping命令:
•主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
•客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
这个指定数量(quorum)可以通过配置文件来配置
当master客观下线之后,sentinel就会重新选一个master选取规则为
- 判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点,断开时间越长不就代表着数据未同步的越多,因此首先排除
- 然后判断每个slave节点的优先级slave-priority的值,值越小优先级越高,如果是0则用不参加选举,刚开始优先级默认值都一样
- 如果优先级一样,则判断slave节点的offset值(偏移量,在repl_baklog中的偏移量),越大说明数据越新,那么优先级越高
- 最后如果偏移量都一样,那么就随便挑一个作为主节点,一般选取slave节点的运行id值,越小优先级越高
选中slave节点后怎么实现故障转移?
当选中了一个slave为新的master节点后,故障转移步骤如下:
- 1、sentinel会给备选的slave1节点发送slaveof no one(永不为奴)命令,让该节点称为master
- 2、sentinel会给所有其他的slave发送slaveof 192.168.64.211:7001(选取的新master地址)命令,让这些slave称为新的master的从节点,开始从新的master上同步数据
- 3、最后,sentinel会强制更改原来故障Master节点的配置文件,将其标记为slave节点标记命令为(slaveof 192.168.64.211;7001,新master地址),当故障节点恢复后自动称为新master的从节点
3.1.4.小结
Sentinel的三个作用是什么?
- 监控
- 故障转移
- 通知
Sentinel如何判断一个redis实例是否健康?
- 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
- 如果大多数sentinel都认为实例主观下线,则判定服务下线
故障转移步骤有哪些?
- 首先选定一个slave作为新的master,执行slaveof no one
- 然后让所有节点都执行slaveof 新master
- 修改故障节点配置,添加slaveof 新master
分片集群
有什么用
分片集群主要是为了解决
1、海量数据存储问题
2、高并发写的问题
因为我们通过主从、哨兵模式可以解决redis中的高并发读问题和高可用问题但并没解决该问题
原理
首先分片集群的特征就是
- 集群中有多个master节点、每个master保存不同的数据
- 每个master都可以由多个从节点(slave)
- master之间同样是通过ping监测彼此的健康状态
- 客户端可以访问集群中的任意节点,最终都会被路由转发到正确的节点位置
注意分片集群中默认不是采用读写分离的形式,默认情况下,所有的读写操作都会发送到负责相应哈希槽的主节点(master node)。如果需要读写分离那么需要再重新配置读写分离配置
分片原理
插槽原理
redis分片集群主要采用插槽来进行分片存储,即redis中会将16384个插槽分配给你设置的主节点,就相当于每个主节点会映射到某段插槽范围,当我们进行写数据或者读数据的时候都会先根据你要读写的key,来计算这个key中用于计算哈希值得有效部分,得到对应的哈希值,然后将哈希值对16384进行取余得到该key对应的插槽位置,得到该位置后,就会看看该插槽位置是属于哪个主节点所管理的插槽区间,然后就找到该主节点进行读写操作。
redis中为甚么要将数据和插槽进行绑定
为什么我们的key是跟插槽进行绑定,而不是直接根redis进行绑定,这是因为redis的主节点是可能出现宕机的情况或者说redis可能进行集群扩容或集群收缩使得master节点增加或者减小,导致节点上的数据丢失了,
但是如果key跟着插槽绑定,当节点宕机时我们可以将这个节点对应的插槽转移到活着的节点,集群扩容时,我们也可以将插槽进行转移,这样数据跟着插槽走就可以永远的找到数据的位置
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
其中有效key的使用规则
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含"{}",且"{}"中至少包含1个字符,"{}"中的部分是有效部分
- key中不包含"{}",整个key都是有效部分
使用场景:
- 同一类数据固定保存在同一个Redis实例,避免访问同一类数据时进行路由重定向,耗费资源
如何将同一类数据固定保存在桶一个Redis实例?
因为如果这类数据不在同一台主节点上,那么我们在查询数据的时候就会有对请求的重定向,而重定向需要重新建立连接,性能上会有一定损失,因此如果将同一类数据放在同一台实例上那么在查询该类数据时我们就只从这台实例上去查,这样就避免了重定向问题
解决方法:让着一系列同类型的数据在存放时,让该类数据key的有效部分相同,那么计算出来的插槽值就一定相同了,从而达到存放在同一态实例上的效果,级让该类数据的key将相同的有效部分用大括号括起来
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
简单举例和使用细节
路由重定向演示
如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到70003节点。
到了7003后,执行get num
时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点
执行一次对key,的访问操作
Redis中如何判断某个key应该在哪个实例中(在哪台主节点上)
- 将16384个插槽分配到不同实例上
- 根据key的有效部分计算出哈希值,对16384取余,
- 余数就是对应的插槽位置,然后看看这个插槽位置是在哪个主节点映射的插槽范围下,最终得到该key的结果
分片集群扩容、缩容原理
扩容
当你想要在集群中添加一个节点,
- 如果想添加从节点那只执行add-node命令时要指明要成为谁的从节点
- 如果是想要添加主节点的话,就要执行add-node命令(该命令中需要指明添加节点的ip和端口,和现在集群中的任意一个节点,用于通知集群),此时刚添加的主节点的插槽默认是0个,所以所有的写操作都不会分配到该新加的主节点上,因此我们需要为其分配插槽
-
- 根据redis-cli --cluster help查出分配插槽的命令为redis-cli --cluster reshard然后分局命令看是从哪个已分配插槽的节点上转移一部分插槽作为新节点的插槽
删除:
在对主节点进行删除操作时,首先需要先将该节点上已经分配的插槽进行转移,然后此时该节点插槽数为空就可以执行 del-node操作
案例
需求:向集群中添加一个新的master节点,并向其中存储 num = 10
- 启动一个新的redis实例,端口为7004
- 添加7004到之前的集群,并作为一个master节点
- 给7004节点分配插槽,使得num这个key可以存储到7004实例
这里需要两个新的功能:
- 添加一个节点到集群中
- 将部分插槽分配到新插槽
想要完成该扩容任务,需要核心在于,创建一个节点将其设置为主节点,然后看看key为num的哈希槽位置在哪,然后通过插槽看看时属于原来的哪个主节点,之后再该主节点中转移一段包含该哈希槽的槽位给新的主节点即可
故障转移
自动故障转移:
redis集群具有自动的主从切换功能即不需要哨兵,当你的主节点宕机了,那么会将该主节点的从节点选一个
来充当主节点,然后当原来的主节点重新恢复,此时会将其设置为原来从节点的从节点
手动故障转移
为甚要有手动手动故障转移?
假设这样一种场景:我们这个集群中7001这个master但是他的及其需要进行维护升级,此时我们启动一个性能比较好的新节点作为7001这个节点的从节点,然后手动的将其替换成7001作为主节点(手动是因为,如果自动故障转移,那么选取的节点是我们不受控的,即当我们从节点数不止一个时,那么就不一定能选取到我们想要的新节点作为主节点)
怎么做:
在新的子结点上利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
这种failover命令可以指定三种模式:
- 缺省:默认的流程,如图1~6歩
- force:省略了对offset的一致性校验
- takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见
出现脑裂问题怎么保证集群还是高可用的
什么是脑裂问题
脑裂说的就是当我们的主节点没有挂,但是因为网络延迟较大,然后和主节点相连的哨兵通信较差,之后主从之间的同步就会受影响,主节点和哨兵之间的信号也不好,但是客户端和主节点之间还能进行通信,此时向主节点更新了一条数据,而由于网络问题,从节点未能同步,且由于哨兵集群和主节点之间的通信较差每次ping都收到主节点的恢复,那么哨兵集群就会判断主节点从主观下线到了客观下线(超过了设置的客观下线的数量),那么此时哨兵就会认为主节点挂了,此时会选取一个哨兵作为主节点,然后强行修改原来主节点的配置将其设置为该slave节点的从节点(然后向新的主节点发起主从同步的时候就会进行全量同步,因此原来主节点中更新的那个消息就会彻底丢失),但是此时的问题就是新的从节点并没有原来更新的那条数据的信息,这样就造成了数据的不一致。这就是脑裂问题
解决的核心思路就是
由于是因为主节点能处理客服端的更新操作和,网络延迟导致主从不一致,然后又由哨兵选取从节点变更为主节点导致的数据丢失,那么我们就可以采取这样的操作,即当发生这种网络延迟问题时,我们就拒绝客服端发过来的请求,这样就保证网络延迟期间主从的一致性,此时就算哨兵将从节点设置为新的主节点也没关系,因为数据被没有被更新,从节点中还是有所有数据
具体解决
具体解决就可以在配置文件中进行配置主节点发现从节点下线或者通信超时的总数量小于阈值
当主节点发现从节点下线或者通信超时的总数量小于阈值时,那么禁止主节点进行写数据,直接把错误返回给客户端。
在 Redis 的配置文件中有两个参数我们可以设置:
- min-slaves-to-write x,与主节点能连接的同的从节点个数只有超过x时才能进行写操作,否之拒绝写操作
- min-slaves-max-lag x,主从数据同步的延迟不能超过 x 秒,如果超过,主节点会禁止写数据。
我们可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。
这就表示这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户