😀 这里写文章的前言:
Redis的主从,哨兵,集群

📝 主从复制

主从复制是Redis分布式基石,也是Redis高可用的保障

被复制的服务器称为主服务器(Master)

对主服务器进行复制的服务器称为从服务器(Slave)

1个master节点,多个slave节点

master 节点负责写(可读可写)

slave 节点负责读(只读),分担压力,slave接受master节点的同步信息一直保持同步

Redis主从,哨兵,集群

配置redis.config

多个redis.config配置, 配置不同的端口启动, 从节点 slave 主节点. 需要手动配置

3种方式

replicaof(Redis 5.0 之前使用 slaveof)

  • 在 slave 直接执行命令:replicaof
  • 在 slave 配置文件中加入:replicaof
  • 使用启动命令:–replicaof

主节点不需要修改配置,从节点需要配置 replica of master ip poid

原理

  • 2.8版本以前,从服务器对主服务器的同步需要从服务器向主服务器发送sync命令来完成,主从服务器需要占用的资源:每次都执行一次RDB持久化
缺点记录
  1. 主服务器执行bgsave生成rdb文件,会占用大量的CPU,磁盘I/O和内存资源
  2. 主服务器将生成的RDB文件发生给从服务器,会占用大量网络宽带
  3. 从服务器接受RDB文件并载入,会导致从服务器阻塞,无法提供服务

Redis主从,哨兵,集群

  • 2.8版本以后,减少全量同步(full resynchronizaztion)的发生,尽可能使用增量同(partial resynchronization). 使用psync命令代替了sync命令来执行同步操作;psync命令同时具备全量同步和增量同步的功能
psync命令
  • 全量同步与上一版本(sync)一致
  • 增量同步中对于断线重连后的复制,会根据情况采取不同措施;如果条件允许,仍然只发送从服务缺失的部分数据。
  • 偏移量过大, 还是会触发全量同步
  • 在bgsave的过程中,新生成的数据会放到环形buffer中, rdb完成后,再传输buffer数据
  • 如果bgsave过程中,buffer超出了最大限制,会触发全量同步

Redis主从,哨兵,集群

主从复制过程

  1. 主从之间建立链接,由于从节点皮质了replica of主ip,此时是知道主节点ip地址的
  2. 从节点发送一个psync ? -1 的命令给主节点,主节点接受到后执行bgsave,发送给从节点,psync包括二个参数: 主服务器的runID和复制进度offset;第一次同步时,其值为-1,主节点响应的fullersync代表全量复制
  3. 从节点接收到rdb文件,清空自己的数据,执行全量同步,后续有新的数据,通过replication Buffer进行增量同步
  4. 加入主从之间的差距还是过大,还是会进行全量同步,buffer超过了大小限制,也会触发一次全量同步

Redis主从,哨兵,集群

?如果是主服务切换时,是否也能进行增量同步

psync2跑了服务器运行id,采用了replid和replid2来代替,其中replid存储的是当前主服务的允许id,replid2保存的是上一个主服务器运行ID

  • 主服务器运行id(replid)
  • 上个主服务器运行id(replid2)

通过replid和replid2可以解决主服务器切换时候,增量同步问题:

  • 如果replid等于当前主服务器的运行id,那么判断同步方式增量/全量同步
  • 如果replid不想等,则判断replid2是否相等(是否同属于上一个主服务器的从服务器),如果相等,依然可以选择增量/全量同步;如果不想等则只能进行全量同步
?主从库间网络断了怎么办

redis2.8之前,断开后重新连接会出现一次全量复制,开销非常大

从redis2.8开始,网络断开之后,主从库采用了增量复制的方式继续同步;增量复制只会把主从库从网络断开期间主库受到的命令,同步给从库

增量复制时,主从库之间具体是怎么保持同步的呢?这里的奥妙就在于 repl_backlog_buffer 这个缓冲区。我们先来看下它是如何用于增量命令的同步的。当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区

如果replaceBuffer满了,也会触发重新的全量复制

Redis主从,哨兵,集群

?为什么主从库的复制不使用AOF
  • RDB文件是二进制文件,无论是要把RDB写入磁盘,还是通过网络传输RDB,IO效率都比记录和传输AOF的高
  • 在从库断进行恢复时,用RDB的恢复效率要高于用AOF
如果主从之间,从节点过多,可能导致主节点传输RDB文件效率问题,可以采纳树结构

Redis主从,哨兵,集群

哨兵模式

主从模式都是手动处理的,假设出了问题大部分不能第一时间解决主从故障等问题,哨兵模式可以很好的解决自动化监听并且处理主从故障的问题(保证服务的高可用);推选出新的master,并且将旧的master退级为slave

监控

哨兵会每隔1秒给所有的主从节点发送PING命令,当主从节点受到PING命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否正常运行

Redis主从,哨兵,集群

发现故障

  • 如果主节点或者从节点没有在规定的时间内响应哨兵,哨兵就会将它们标记为 [主观下线]
  • 并且告知其他哨兵,ping该节点,超过半数确认是否 [客观下线]

Redis主从,哨兵,集群

故障转移

1.选出leader哨兵

为了更加“客观”的判断主节点故障了,一般不会只由单个哨兵的检测结果来判断,而是多个哨兵一起判断,这样可以减少误判概率,所以哨兵是以哨兵集群的方式存在的

需要在哨兵集群中选出一个leader,让leader来执行主从切换;选举leader的过程其实是一个投票的过程,在投票开始前,肯定得有个 [候选者]

举个栗子:假设有三个哨兵;当哨兵B先判断到主节点[主管下线后],就会给其他实例发送is-master-down-by-addr命令;接着,其他哨兵会根据自己和主节点的网络连接情况,做出赞成投票或者拒绝投票的响应

当哨兵 B 收到赞成票数达到哨兵配置文件中的 quorum 配置项设定的值后,就会将主节点标记为「客观下线」,此时的哨兵 B 就是一个Leader 候选者

候选者会向其他哨兵发送命令,表明希望成为Leader来执行主从切换,让其他哨兵对它投票

每个哨兵只有一次投票机会,如果用完了就不能参与投票,可以投给自己或投给别人,但是只有候选者才能把票投给自己

那么在投票的过程中,任何一个 候选者 , 要满足两个条件:

  • 第一,拿到半数以上的赞成票
  • 第二,拿到的票数同时还需要大于等于哨兵配置文件中的quorum值

Redis主从,哨兵,集群

哨兵节点至少要有3个

如果哨兵集群中有2个哨兵节点,此时如果一个哨兵想要成为leader,必须获得2票,而不是1票;所以,如果哨兵集群中有个哨兵挂掉了,那么就只剩一个哨兵了,如果这个哨兵想要成为 Leader,这时票数就没办法达到 2 票,就无法成功成为 Leader,这时是无法进行主从节点切换的

因此,通常我们至少配置3个哨兵节点; 这时,如果哨兵集群中有哨兵挂掉了,那么还剩下两个哨兵,如果这个哨兵想成为leader,这时还是有机会达到2票的,所以还是可以选举成功的,不会导致无法进行主从节点切换

你要问,如果 3 个哨兵节点,挂了 2 个怎么办?这个时候得人为介入了,或者增加多一点哨兵节点。

2.选举新节点

**第一步,从已下线的主节点属下的所有从节点中,挑选出一个状态良好,数据完整的从节点,然后向这个从节点发送 SLAVE no one 命令 ,将这个从节点转换为主节点**

先剔除网络不好的

  • 首先要把网络状态不好的从节点给过滤掉( 5 秒内未回复 info 命令的 )
  • 首先把已经下线的从节点过滤掉
  • 然后把以往网络连接状态不好的从节点也给过滤掉。(down-after-milliseconds * 10)

第一轮考察: 优先级最高的从节点胜出

slave-priority 配置项,可以给从节点设置优先级

第二轮考察:选择复制偏移量最大,也就是复制最完整的从节点

如果某个从节点的 slave_repl_offset 最接近 master_repl_offset,说明它的复制进度是最靠前的,于是就可以将它选为新主节点

第三轮考察:ID 号小的从节点胜出

什么是 ID 号?每个从节点都有一个编号,这个编号就是 ID 号,是用来唯一标识从节点的

Redis主从,哨兵,集群

选举完成后,哨兵leader向被选中的server2从节点发送 SLAVEOF no one命令,让这个从节点接触从节点的身份,将其变为新主节点

Redis主从,哨兵,集群

3.从节点指向新主节点

当新主节点出现之后,哨兵 leader 下一步要做的就是,让已下线主节点属下的所有「从节点」指向「新主节点」,这一动作可以通过向「从节点」发送 SLAVEOF 命令来实现

Redis主从,哨兵,集群

所有从节点指向新主节点后的拓扑图如下:

Redis主从,哨兵,集群

4.通知客户端的主节点已更换

经过前面三步,哨兵集群终于完成主从节点切换的工作,那么新节点的信息如何通知给客户端呢?

这主要通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息

Redis主从,哨兵,集群

客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道

主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的ip地址和端口信息,这个时候客户端就可以接收到这条信息,然后用这里面的新主节点的ip地址和端口进行通信

5.将旧主节点变为从节点

故障转移操作最后要做的是,继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点,如下图

Redis主从,哨兵,集群

哨兵集群如何工作

前面提到了 Redis 的发布者/订阅者机制,那就不得不提一下哨兵集群的组成方式,因为它也用到了这个技术

在我第一次搭建哨兵集群的时候,当时觉得很诧异。因为在配置哨兵的信息时,竟然只需要填下面这几个参数,设置主节点名字、主节点的 IP 地址和端口号以及 quorum 值

sentinel monitor <master-name> <ip> <redis-port> <quorum>

不需要填其他哨兵节点信息,是如何感知对方的,又是如何组成哨兵集群的?

哨兵节点之间通过redis的发布者/订阅者机制来互相发现的

在主从集群中,主节点上有一个名为__sentinel__:hello的频道,不同哨兵就是通过它来相互发现,实现互相通信的

在下图中,哨兵 A 把自己的 IP 地址和端口的信息发布到__sentinel__:hello 频道上,哨兵 B 和 C 订阅了该频道。那么此时,哨兵 B 和 C 就可以从这个频道直接获取哨兵 A 的 IP 地址和端口号。然后,哨兵 B、C 可以和哨兵 A 建立网络连接

Redis主从,哨兵,集群

通过这个方式,哨兵 B 和 C 也可以建立网络连接,这样一来,哨兵集群就形成了

脑裂问题

旧的master出了问题,有一些还没同步到redis上,从redis通过选举成为一个新的master,有新的master起来后,由于一些问题,哨兵通知不到旧的master,导致2个master,就出现了脑裂

本质是2个master,新master是可以接受写入数据的, 但是有一些数据写到旧的mater上了, 但是从redis 只会同步新的mater, 当旧mater的恢复正常后, 哨兵会把旧的mater降级为从, 降级是要清除数据的,清了数据 之前写在旧mater上的数据还没同步的也就没了

问题出在,当从升级为master的时候,有2个master,并且2个都在接受写入数据;旧的 新的master都在写入数据,但是从的只同步了新的master,写进旧的master的数据没同步,并且随着自己的降级也没了,丢失了

?脑裂如何处理

Redis主从,哨兵,集群

# master 须要有至少 x 个副本连接。
min-slaves-to-write x
# 数据复制和同步的延迟不能超过 x 秒。
min-slaves-max-lag x

注意高版本中,redis已经修改了这2个配置

# min-replicas-to-write x
# min-replicas-max-lag x

CAP理论认为,一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三个基本需求,最多只能同时满足其中两项。

在Redis哨兵模式下出现脑裂问题时,主从节点之间的分区容错性得以保证,但同时需要在一致性和可用性之间做出取舍。

首先,由于数据复制和同步的延迟可能会导致主从节点之间的数据不一致,因此在Redis的哨兵模式中,我们需要配置 min-slaves-to-writemin-slaves-max-lag 这两个参数,以保证数据的一致性。

其次,在节点出现脑裂时,如果旧的主节点和新的主节点都在接受写入数据,那么可能会出现数据写入不一致的情况。为了解决这个问题,我们可以在哨兵模式中配置 min-replicas-to-writemin-replicas-max-lag 这两个参数,以保证数据的一致性和可用性。

但是需要注意的是,以上的配置都只能减少发生脑裂的可能性,而不能完全避免。因此,对于Redis的哨兵模式来说,我们需要在一致性和可用性之间做出取舍,根据具体的业务需求来设置合理的参数。

  • redis集群是可以让脑裂存在的,其实是一种高可用的特性,但不保证数据一致
  • redis通过设置两个参数,一定程度上其实是降低可用性,以提供数据一致性
  • 为什么愿意降低可用性?因为那部分的数据会因为主从切换而丢失,所以宁愿不可用

Cluster集群

哨兵模式最大的缺点就是所有的数据都放在同一台服务器上,无法较好的进行水平扩展

为了解决哨兵模式存在的问题,集群模式应运而生。在高可用上,集群基本是直接复用的哨兵模式的逻辑,并且针对水平扩展进行了优化

Redis Cluster集群模式具备的特点如下:

  1. 采取去中心化的集群模式,将数据按照槽存储分布在多个redis节点上。集群共有16382槽,每个节点负责处理部分槽
  2. 使用CRC16算法来计算key所属的槽,CRC16(key,key len) &16383
  3. 所有Redis节点批次互联,通过PING-PONG机制来节点间的心跳检测
  4. 分片内采用一主多从保证高可用,并提供复制和故障恢复功能。在实际中,通过会将主从分布在不同的机房,避免机房出现故障导致整个分片出问题
  5. 客户端与Redis节点直连,不需要中间代理层(Proxy);客户端不需要连接集群所有节点,连接集群中任何一个可用的节点即可

Redis主从,哨兵,集群

目前redis的分区规则是: hash分区,分成了 16384 个哈希槽

RedisCluster采用了哈希分区的“虚拟槽分区”方式(哈希分区分节点取余,一致性哈希分区和虚拟槽分区)

Redis主从,哨兵,集群

集群之后的扩容

如何保证集群在线扩容的安全性? Redis集群要添加分片,槽的迁移怎么保证无损

比如集群已经对外提供服务,原本有3分片,准备新增2个,怎么在不下线的情况下,无损的从原有的3个分片指派若干槽这2个分片?

Redis使用ASK错误来保证在线扩容的安全性;在槽的迁移过程中若有客户端访问,依旧先访问源节点,源节点会在自己的数据库里查找指定的键,如果找得到的话,就直接执行客户端发送的命令

如果没有找到,说明该键可能被迁移到目标节点了,源节点将向客户端返回一个ASK错误,该错误会将客户端转向正在倒入槽的目标节点,并再次发送之前要指令的命令,从而获取结果

故障发现

  1. Redis Cluster通过ping/pong消息实现故障发现:不需要sentinel
  2. ping/pong不仅能传递节点与槽的对应消息,也能传递其他状态,比如:节点主从状态,节点故障等
  3. 故障发现就是通过这种模式来实现,分为主观下线和客观下线

主观下线

  • 某个节点认为宁外一个节点不可用,‘偏见’,只代表一个节点对另外一个节点的判断,不是所有节点的认知
  • 如果节点1发现与节点2最后通信时间超过node-timeout,则把节点2标识为pfail状态

Redis主从,哨兵,集群

客观下线

  1. 当半数以上持有槽的主节点都标记某节点主观下线时,可以保证判断的公平性
  2. 集群模式下,只有主节点(master)才有读写权限和集群槽的维护权限,从节点(slave)只有复制的权限
  3. 客观下线流程:

1.某个节点接收到其他节点发送的ping消息,如果接收到的ping消息中包含了其他pfail节点,这个节点会将主观下线的消息内容添加到自身的故障列表中,故障列表中包含了当前节点接收到的每一个节点对其他节点的状态信息

2.当前节点把主观下线的消息内容添加到自身的故障列表之后,会尝试对故障节点进行客观下线操作

Redis主从,哨兵,集群

准备选举时间

使偏移量最大的从节点具备优先级成为主节点的条件

Redis主从,哨兵,集群

替换主节点

当前从节点取消复制变成离节点(slave of no one)
执行cluster del slot撤销故障主节点负责的槽,并执行cluster add slot把这些槽分配给自己
向集群广播自己的pong消息,表明已经替换了故障从节点

集群选举流程

故障转移的第一步就是选举出新的主节点,以下是集群选举新的主节点的方法:

1)当从节点发现自己正在复制的主节点进入已下线状态时,会发起一次选举:将 currentEpoch(配置纪元)加1,然后向集群广播一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。

2)其他节点收到消息后,会判断是否要给发送消息的节点投票,判断流程如下:

  1. 当前节点是 slave,或者当前节点是 master,但是不负责处理槽,则当前节点没有投票权,直接返回。
  2. 请求节点的 currentEpoch 小于当前节点的 currentEpoch,校验失败返回。因为发送者的状态与当前集群状态不一致,可能是长时间下线的节点刚刚上线,这种情况下,直接返回即可。
  3. 当前节点在该 currentEpoch 已经投过票,校验失败返回。
  4. 请求节点是 master,校验失败返回。
  5. 请求节点的 master 为空,校验失败返回。
  6. 请求节点的 master 没有故障,并且不是手动故障转移,校验失败返回。因为手动故障转移是可以在 master 正常的情况下直接发起的。
  7. 上一次为该master的投票时间,在cluster_node_timeout的2倍范围内,校验失败返回。这个用于使获胜从节点有时间将其成为新主节点的消息通知给其他从节点,从而避免另一个从节点发起新一轮选举又进行一次没必要的故障转移
  8. 请求节点宣称要负责的槽位,是否比之前负责这些槽位的节点,具有相等或更大的 configEpoch,如果不是,校验失败返回。

如果通过以上所有校验,那么主节点将向要求投票的从节点返回一条 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,表示这个主节点支持从节点成为新的主节点。

3)每个参与选举的从节点都会接收 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 消息,并根据自己收到了多少条这种消息来统计自己获得了多少个主节点的支持。

4)如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1 张支持票时,这个从节点就会当选为新的主节点。因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有 N个主节点进行投票,那么具有大于等于 N/2+1 张支持票的从节点只会有一个,这确保了新的主节点只会有一个。

5)如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

这个选举新主节点的方法和选举领头 Sentinel 的方法非常相似,因为两者都是基于 Raft 算法的领头选举(leader election)方法来实现的

ASK错误

当redis正在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移

当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息

如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制

Redis主从,哨兵,集群

moved重定向

是路由层面的,直接修改迁移之后的槽映射关系,不用像ask错误一样,返回错误再重新路由

Redis主从,哨兵,集群

**moved异常与ask异常的相同点和不同点**
  1. 两者都是客户端重定向
  2. moved异常: 槽已经确定迁移,即槽已经不在当前节点
  3. ask异常: 槽还在迁移中

集群不可用时机

  • 挂掉的master没有对应的slave,即有任一slot不可用,集群都不可用
  • 超过半数master都挂掉了,不管有没有slave,都将不可用

🤗 总结归纳

📎 参考文章

💡 有关文章的问题,欢迎您在底部评论区留言,一起交流~