哨兵方案的缺陷

sentinel只是监控master节点的状态,如果master节点异常,则重新进行主从切换,将某一台slave作为master。
sentinel方案配置复杂,并且性能和高可用性等各方面表现 一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置得过大(redis单节点最好小于10G),否则会导致持久化文件过大,影响数据恢复或主从同步的效率。

高可用集群方案

image.png
redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。
redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点。可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。

集群原理分析

Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的slots槽位配置信息并将其缓存在客户端本地。这 样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
Redis通过什么算法来计算key放入哪个slots?
getCRC16(key)&(16384-1) //通过CRC算法将key进行hash得到整数值,通过位运算定位slots位置。

跳转重定位

客户端向节点发出一个指令,该节点发现指令的key所在的slots不归自己管理(比如key经过hash计算后slots为5723,但该节点所分配的slots范围为1000-1500,这种情况则定位不了槽位)。此时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。
客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的slots映射表缓存,后续所有key将使用新的slots映射表。
image.png

集群节点间的通信机制

  • 集中式:实现方式如zookeeper;
  • gossip协议(默认)
    • ping:节点间互相发送ping交换元数据信息;
    • pong:对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新;
    • meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信;
    • fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了;

gossip协议优缺点

  • 优点:元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
  • 缺点:元数据更新有延时可能导致集群的一些操作会有一些滞后;

网络抖动
假如不同的机房网络不太好的情况下,突然节点之间访问延迟或者出现短暂的访问不了的情况(过几秒又连接上),为了避免因为这种场景而导致cluster频繁切换(数据的重新复制)造成性能消耗,该如何解决?
Redis Cluster提供“cluster-node-timeout”,表示如果超过这个时间,则认定该节点出现故障,需要切换!

Redis集群选举原理

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期待成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:

  1. slave发现自己的master变为FAIL;
  2. 将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息;
  3. 其他节点收到该信息(只有master响应),判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack;
  4. 尝试failover的slave收集master返回的FAILOVER_AUTH_ACK;
  5. slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的);
  6. slave广播pong消息通知其他集群节点;

如果重新选举时出现同一时刻多个slave发送请求给master节点进行选举,可能会出现选举失败,而且还可能会多次失败,如何避免此问题?
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票

  • 延迟计算公式:DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
  • SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。

Redis集群选举
image.png

Redis集群“脑裂”数据丢失问题

redis集群没有过半机制会有脑裂问题
网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点(因为从节点初始化或启动时会删掉本地的所有数据,然后再从主节点同步数据),这时会有大量数据丢失。
解决方案
min‐replicas‐to‐write 1 //写数据成功的前提是最少同步的slave节点数,这种机制类似zookeeper的“zab机制”,比如集群总共三个节点,加上master就是2,>=半数。
注:脑裂问题的解决方案有个弊端,如果需要至少一个slave节点同步数据成功才算成功的话,就可能引发另一个问题,redis本就是“AP”机制(参看CAP机制),如果改变这样的机制,使其成为“CP”机制,一旦slave节点写入失败,就会造成redis返回失败,就牺牲了可用性。
集群是否只有在完整的情况才能对外提供服务?
cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
Redis和Zookeeper的脑裂问题和解决方案很类似,都是需要半数或半数以上数据同步成功就能避免脑裂问题。

Redis集群对大批量操作命令的支持

redis集群只支持将key落到同一个slots中
解决方案:mset {xx}:1:name java {xx}:1:age 20 //{xx} 表示数据分片时,hash计算的只会是大括号里的值,确保不同的key落到同一个slots中。
Redis集群架构下的数据倾斜问题如何解决?
1.使用本地缓存
2.利用分片算法的特性,对key进行打散处理
3.big key形成集群数据量倾斜:对big key进行拆分

问题一:Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
3个master节点和4个master的场景
奇数master节点可以在满足选举该条件的基础上节省一个节点。

  • 3个或4个master节点如果挂了一个节点,是可以重新选举新的master节点;
  • 3个或4个master节点如果挂了两个节点,则都无法选举新的master节点(因为都无法满足“半数成功”原则);

总结:所以奇数的master节点更多的是从节省机器资源角度出发

问题二:mysql 为什么用自增列作为主键或者其他顺序ID

1、如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引。
如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引。
如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。
2、数据记录本身被存于主索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放
因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)
3、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页
4、如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销
同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。

问题三:redis数据如何分片?

Redis Cluster 采用的是虚拟槽分区,一个集群共有 16384 个哈希槽,Redis Cluster会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N个。
Redis Cluster 会对 key 进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。

问题四:为什么使用数据索引能提高效率?

数据索引的存储是有序的。在有序的情况下,通过索引查询一个数据是无需遍历索引记录的。极端情况下,数据索引的查询效率为二分法查询效率,趋近于log2(N)。

问题五:为什么脑裂会导致数据丢失?

主从切换后,从库一旦升级为新主库,哨兵就会让原主库执行 slave of 命令,和新主库重新进行全量同步。而在全量同步执行的最后阶段,原主库需要清空本地的数据,加载新主库发送的 RDB 文件,这样一来,原主库在主从切换期间保存的新写数据就丢失了。