4.哨兵模式实现原理
- 监控:当哨兵启动时,会监控所有的主从节点,并且保证哨兵集群能够正常通信,这个阶段称为监控阶段
- ping:哨兵节点启动后所有哨兵每隔1S会互相发送ping心跳,来检测其他哨兵节点是否正常
- info:哨兵节点启动后每隔10S会向所有的主节点发送info指令,得到runid、role、各个slave的详细信息。再根据slave信息发送info指令获取slave的详细信息,会得到runid、role、master_host、master_port、offset...
- pub/sub:每个哨兵节点每隔2S会在一个指定频道发布当前节点保存的主节点信息,其他哨兵节点会订阅该频道获取信息
- 通知:当哨兵发现主节点或者从节点出现故障时,当前哨兵会通知其他哨兵,其他哨兵挨个发送ping命令来到出故障的redis节点,如果redis节点没有响应则哨兵集群认为此节点出现故障,我们把这个阶段称为哨兵通知阶段
- 哨兵监控slave
- 在哨兵集群中,如果某一个哨兵节点发现某个从节点出现故障后,会将其标记为+sdown,并发布订阅告知给其他哨兵节点,其他哨兵节点收到通知后会将次从节点剔除,并标记为+sdown,等到下次slave回复哨兵时,哨兵会将其从新变为从节点,并标记为-sdown
- 哨兵监控master
- 某个哨兵发现主节点出故障后,会将当前主节点标记为+sdown,也就是标记为主观下线,单个哨兵认为有问题可能是网络抖动问题
- 后续如果其他哨兵节点也发现此主节点出现问题,当发现问题的哨兵数量大于quorym时,那么哨兵集群会认为此主节点出现故障,标记为odown状态,此时主节点被标记为客观下线
- 故障转移:当哨兵集群已经发现某个节点出现故障时,如果是从节点则直接剔除,如果是master节点出现故障,那么需要在多个从中推选出一个新的主节点,我们把这个阶段称为故障转移阶段
- 哨兵leader选举:当主节点被标记为客观下线时,就要在哨兵集群中选出一个哨兵节点来完成后面的故障转移工作,每个哨兵节点都可以成为leader,选举流程如下
- 主节点被标记为客观下线时,哨兵节点会向其他哨兵节点发送is-master-down-by-addr命令,表明自己想成为leader来处理master的故障转移
- 当其他哨兵节点收到此命令时,可以同意或者拒绝它成为leader
- 如果票数大于哨兵集群数量的一半时将成为领导者,如果没有大于,则继续开始下一轮选举,并且票数会类加
- 故障转移实现流程:哨兵在监控阶段时就保存了主从节点信息,选举出leader哨兵后,哨兵要开始进行主从节点故障转移,从众多从节点中选举出一个主节点。主节点选举流程如下
- 过滤掉已经下线的从节点
- 优先级高的推选为主节点,默认都是100,可通过slave-priority参数进行设置,越小优先越高
- 推选出offset最大的节点,offset大意味着和主节点数据越相近
- 推选出runid最小的节点,因为runid越小代表越早加入集群,具有更多的复制数据,更适合成为主节点
- 更新状态:当故障转移后,需要更新当前的主从状态,流程如下
- 被选举出来的新master将执行slaveof no one 命令,断开与原master连接,将自己从从节点转换为主节点,并在其他的slave配置文件上加上slaveof命令
- 将已经剔除的主节点设置为新主节点的从节点,后续恢复时,将其变为从节点,并在其配置文件中加上slaveof命令
5.主观下线和客观下线
- 名词说明
- 主观下线:某个哨兵节点单方面的认为一个Redis节点不可用了
- 客观下线:大部分哨兵节点都认为一个Redis节点不可用时,那么此节点就被认为是客观下线
- 原理
- 哨兵进程会使用PING命令的方式来检测各个主库和从库的网络连接情况,用来判断实例状态
- 如果哨兵发现主库或者从库响应超时(down-after-millisecond),那么哨兵会判定其为"主观下线"
- 如果该节点是主节点,那么哨兵会进一步判断是否需要对其进行故障切换,这时候会发送命令(SENTINEL is-master-down-by-addr)询问其他哨兵节点是否认为该主节点是主观下线,当达到指定数量(quorum)时,哨兵就会认为此主节点是客观下线
- 如果没有足够数量的哨兵同意主节点进入主观下线,主节点的主观下线状态就会被消除,若主节点重新向哨兵的PING命令返回有效回复,主节点的主观下线状态也会被消除
- 哨兵误判:主库本身没有故障,但由于哨兵的误判,判断它为下线状态。一旦启动主从切换,后续的选举和通知操作都会带来额外的计算和通信开销。因此,为了不必要开销,我们要严格注意误判的情况。在哨兵集群中,判定主库是否处于下线状态,不是由一个哨兵来决定的,而是只有大多数哨兵认为主库已经"主观下线",主库才会标记为"客观下线"。这种判断机制为:少数服从多数。同时会触发故障转移
- 关键属性
down-after-milliseconds:Redis Sentinel 配置文件中的一个属性,表示 Sentinel 发送 Ping 命令给实例之后,如果规定的时间内没有收到响应,就认为实例已经下线了,然后 Sentinel 就会开始判断实例的状态。这个属性的默认值为 30,000 毫秒 (30 秒)。可以通过修改配置文件的方式来更改该属性的值 SENTINEL is-master-down-by-addr:Redis Sentinel 提供的命令之一,用于向其它 Sentinel 节点询问某个主服务器是否已经下线。当一个 Sentinel 节点判定某个主服务器为主观下线后,会向其它 Sentinel 节点发送 SENTINEL is-master-down-by-addr 命令来确认这个主服务器是否已经客观下线,从而决定是否执行自动故障转移。这个命令需要提供主服务器的 IP 地址和端口号作为参数,响应内容则是一个字符串,表示主服务器的状态信息
quorum:哨兵模式中用来决定主节点是否被判定为客观下线的数量阈值。当有N个哨兵节点监控同一个主节点时,要求有至少N/2+1个哨兵节点判定该主节点为主观下线,此时才会被判定为客观下线,从而触发故障转移。这个数量阈值可以通过配置文件中的参数quorum来设置 SLAVEOF NO ONE:此命令是Redis用于将一个从节点转化为主节点的命令。执行该命令后,从节点会断开与其原来的主节点的连接,并开始独立工作,即成为一个新的独立的主节点 slaveof:此命令是Redis中用于配置主从复制关系的命令。它用于将一个Redis实例设置为另一个Redis实例的从服务器。这个命令在Redis的主从复制过程中是非常重要的,它让从服务器能够连接到主服务器,并且通过复制主服务器上的数据来保持与主服务器的数据一致性
6.哨兵模式优缺点
- 优点
- 高可用性:哨兵模式可以自动检测节点的可用性,并实现主从节点的自动切换,从而提高了 Redis 的高可用性、健壮性
- 灵活性:哨兵模式支持动态的添加、删除节点,可以根据需要扩展 Redis 的读写性能
- 无需人工干预:哨兵模式不需要人工干预,可以自动实现故障转移和节点恢复
- 缺点
- 延迟:哨兵模式需要检测节点的状态,判断主节点是否宕机,再进行故障转移,因此会增加一定的延迟
- 配置复杂:哨兵模式需要配置多个哨兵节点,需要合理设置节点的数量和 quorum 值,对于不了解 Redis 的开发人员来说,配置较为复杂
- 在线扩容复杂:Redis比较难支持在线扩容
- 所有主从节点保存的都是全量数据,浪费内存空间,没有实现真正的分布式存储,数据量过大时,主从同步严重影响master性能
- 故障转移时,在主节点选举结束之前,谁也不知道主从节点是谁,此时Redis会开启切换保护机制,禁止写操作,直到选举出新的主节点
3、RedisCluster模式
1.RedisCluster模式概述
- RedisCluster是Redis官方提供的分布式方案,适用于数据了特别大的场景,相对于哨兵模式,它具有以下优点
- 高可用:多个主节点,每个主节点有对应多个从节点,主节点宕机RedisCluster机制会自动将某个从节点切换到主节点
- 扩展性
- 横向扩展:通过增加机器实现增加能力上限
- 读写扩展:基于主从模式,通过读写分离,增加读写能力,避免单点故障
- 分布式存储:RedisCluster采用分片技术将数据均匀分布到多个节点上,每个节点只保存部分数据,避免了单个节点存储数据过大的问题,提高了存储容量和性能
- 自动数据迁移:RedisCluster支持自动数据迁移,当新增或删除节点时,会自动将数据迁移到其他节点上,保证数据均衡和数据完整性
- RedisCluster是什么
- redis在3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。cluster模式为了解决Redis单节点容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性
- RedisCluster是一种服务器Sharding技术,分片和路由都是在服务端实现,采用多主多从,每一个分区都是由一个Redis主节点和多个从节点组成,片区和片区之间是相互平行的。RedisCluster集群采用了P2P的网络拓扑架构,没有中心节点,所有节点通过Gossip协议通信,所有节点即存储数据也是控制节点
- RedisCluster建议至少部署3个主节点和3个从节点,以保证数据的高可用性和负载均衡
- 总结:RedisCluster是可以达到主从复制架构、读写分离、哨兵集群、高可用效果的集群架构
2.数据分布算法
- 由于RedisCluster是将数据分布在不同的片区,所以需要数据分布算法,以下是几种数据分布算法的原理优缺点,RedisCluster采用的是HashSlot算法
- Hash寻址算法
- 原理:对Key进行Hash运算,然后将值对主节点数量取模,最后得到的数值就是此数据应存储到的主节点
- 优点:简单、快速、高效、适用于大规模快速查询
- 缺点:
- 数据分布不均匀:如果使用简单的Hash寻址算法,可能会出现数据倾斜的情况,导致某些节点负载过高,而某些节点负载过轻
- 数据迁移困难:使用Hash寻址算法进行数据分布后,如果需要添加或删除节点,就需要重新计算每个数据对应的节点,然后将其迁移到新节点上,这个过程非常繁琐,而且需要暂停对集群的写操作
- 扩展性受限:使用Hash寻址算法进行数据分布时,如果需要增加节点,那么需要将所有的数据重新计算分配,这样就会限制集群的扩展性
- 大量缓存重建问题:主节点如果宕机,那么Hash运算时根据现有存活节点进行取模,得到的数值与原有存储数据时的数值不匹配,请求走不到原有路由节点上,从而导致大量的key瞬间全部失效
- 一致性Hash算法
- 原理:将所有的主节点进行Hash运算,再将Hash值映射到一个固定的Hash环上面。然后对Key进行Hash运算,将此Hash值与圆环上的各个点进行对比,将Hash值落在圆环上后进行顺时针旋转去寻找距离自己最近的一个节点,数据的存储与读取都在此节点进行
- 优点:保证任何一个主节点宕机,只会影响在之前那个主节点上的数据,此前的主节点宕机,查询时这部分数据会丢失,写入时沿着顺时针去到下一个主节点
- 缺点
- 数据丢失过大:在节点比较少的情况下, 丢失的数据量还是非常庞大的
- 缓存热点问题:如果某些数据被频繁地访问,会导致热点数据集中在某个节点上,造成负载不均
- 数据倾斜问题:节点数量较少时,由于数据分布不均,可能会导致某个节点负载过重,影响系统的性能。这是因为Hash函数的输出值在Hash环上并不是均匀分布的,而是有规律的。一致性哈希算法通过引入虚拟节点来解决这个问题,将每个物理节点映射到多个虚拟节点上,使得数据更加均匀地分布在环上
- 一致性问题:由于节点的添加或删除会影响哈希值的计算,可能会导致数据分布不均,这个问题可以通过一些技术手段来解决,例如虚拟节点、数据复制等
- 带虚拟节点一致性Hash算法
- 业界为了解决一致性Hash算法的问题,在一致性Hash算法的基础上增加了虚拟节点机制
- 原理
- 出现数据倾斜的原因就是因为节点的数量较少,那么我们可以通过对主节点Hash运算后,固定到Hash环上面,然后对该主节点进行多次Hash运算计算出几个虚拟节点,当Key进行Hash运算后如果顺时针找到虚拟节点,那么虚拟节点会映射到他真正的主节点,从而达到数据分散的效果
- 实际运用中,通常将虚拟节点设置为32个以上,此时即便节点很少也可以做到尽可能的数据均匀分布
- 举例
- Hash("Neuronet") -> 虚拟节点1
- 虚拟节点1 -> 主节点1
- HashSlot算法
- Redis使用此算法的原因
- RedisCluster需要支持动态扩容和缩容,在一致性hash算法中,增加或删除一个节点会导致整个哈希环重新分布,这样会导致大量的数据迁移和节点负载的不均衡,而使用HashSlot算法则可以避免这个问题
- 此算法可以避免数据倾斜问题和虚拟节点带来的计算负担
- 原理
- 在HashSlot算法中,取值范围是0~16383。Redis将整个key空间分成了16384个槽,也就是16384个slot,每个主节点负责一部分槽
- 客户端根据Key计算CRC16值,将值对16384取模,找到对应的槽,然后根据槽对应的主节点进行数据访问
- Redis中的每个主节点都对应一部分槽位。增加一个主节点时,只需要将其他主节点的槽位分配一部分到新槽位。删除一个主节点时,就将此主节点的槽位移动到其他的主节点上去。移动Hash槽的成本是非常低的
- HashSlot算法还可以将不同的数据类型映射到不同的槽中,以达到更好的负载均衡效果。同时,Redis还支持将相邻的多个槽划分到同一个节点,以便在某些场景下提高数据读取的效率
- 如果想确保一些Key总是被分配到同一个节点,那么您可以使用哈希标签(Hash Tag)功能来强制让这些键映射到同一个槽位。哈希标签是在 RedisKey 上使用大括号 "{}" 的一种特殊语法
- 例如:set mykey1:{100}和set mykey2:{100},它们的hash tag都是{100},那么它们就会被存储在同一个hash slot中
- 16384槽位由来
- CRC16算法产生的Hash值有16bit,该算法可以产生65536个值。值是分布在0~65535之间,那么其实在做取模运算时,我们是可以取模65536的,但是Redsi作者采取了16384
- 作者的回答是:Redis集群节点数量如果超过1000个那么会造成网络拥堵,所以建议节点数量不超过1000个,那么1000个节点使用16384个槽位完全够用了。如果使用65536个槽位会导致主节点之间交互心跳包时,浪费带宽。槽位数量过少不够用,过多浪费带宽,所以作者通过实测计算得出一个16384的值,不多不少刚刚好