摘要

redis的主从复制是redis系统的高可用前提,从库宕机依然可以将请求发送给主库或者其他从库,但是 Master 宕机,只能响应读操作,写请求无法再执行。所以主从复制架构面临一个严峻问题,主库挂了,无法执行写操作,无法自动选择一个 Slave 切换为 Master,也就是无法故障自动切换。

例如:我们有 3 台服务器做了主从复制,一个主服务器 A 和两个从服务器 B、C,当 A 发生故障之后,需要人工把 B 服务器设置为主服务器,同时再去 C 服务器设置成从服务器并且从主服务器 B 同步数据,如果是发生在晚上或者从服务器节点很多的情况下,对于人工来说想要立即实现恢复的难度很多,所以我们需要一个自动的工具——Redis Sentinel(哨兵模式)来把手动的过程变成自动的,让 Redis 拥有自动容灾恢复(failover)的能力。哨兵主要负责的任务是:监控整个redis、选择新的master,同时其他redis服务。

一、哨兵机制相关原理

哨兵是 Redis 的一种运行模式,它专注于对 Redis 实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主及主从切换,实现故障转移,确保整个 Redis 系统的可用性。结合 Redis官方文档,可以知道 Redis 哨兵具备的能力有如下几个:

  • 监控:持续监控 master 、slave 是否处于预期工作状态。
  • 自动切换主库:当 Master 运行故障,哨兵启动自动故障恢复流程:从 slave 中选择一台作为新 master。
  • 通知:让 slave 执行 replicaof ,与新的 master 同步;并且通知客户端与新 master 建立连接。

1.1 redis Sentinel 集群监控原理

1.每个 Sentinel 以每秒一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。

2.如果一个实例距离最后一次有效回复 PING 命令的时间超过指定的值, 那么这个实例会被 Sentinel 标记为主观下线。

3.正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。

4.有足够数量的 Sentinel 在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。

5.每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。

6.Sentinel 和其他 Sentinel 协商主节点的状态,如果主节点处于 SDOWN 状态,则投票自动选出新的主节点。将剩余的从节点指向新的主节点进行数据复制。

7.当没有足够数量的 Sentinel 同意主服务器 下线时, 主服务器的客观下线状态就会被移除。主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器主观下线状态就会被移除

哨兵之间虽然建立连接了,但是还需要和 slave 建立连接,不然没法监控他们呀,如何知道 slave 并监控他们的?

关键还是利用 master 来实现,哨兵向 master 发送 INFO 命令, master 掌门自然是知道自己门下所有的 salve 小弟的。所以 master 接收到命令后,便将 slave 列表告诉哨兵。哨兵根据 master 响应的 slave 名单信息与每一个 salve 建立连接,并且根据这个连接持续监控哨兵。如图所示,哨兵 2 向 Master 发送 INFO 命令,Master 就把 slave 列表返回给哨兵 2,哨兵 2 便根据 slave 列表连接信息与每一个 slave 建立连接,并基于此连接实现持续监控。剩下的哨兵也同理基于此实现监控。

1.2 redis Sentinel 故障自动转移

一次故障转移操作由以下步骤组成:

  • 发现主服务器已经进入客观下线状态。
  • 对我们的当前纪元进行自增, 并尝试在这个纪元中当选。
  • 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。如果当选成功, 那么执行以下步骤。
  • 选出一个从服务器,并将它升级为主服务器。
  • 向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。
  • 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
  • 向已下线主服务器的从服务器发送 SLAVEOF 命令, 让它们去复制新的主服务器。
  • 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。

1.2.1 主动下线与被动下线

 哨兵进程会使用PING命令的方式来检测各个主库和从库的网络连接情况,用来判断实例状态如果哨兵发现主库或者从库响应超时,那么哨兵会判定其为"主观下线"。如果哨兵检测从库,发现从库在规定时间内未响应,那么哨兵就会把它标记为"主观在线",因为从库的下线影响一般不太大,集群的对外服务不会间断。但是,如果检测主库,哨兵不会简单把它标记为"主观在线",开启主从切换。

因为很有可能会有一种特殊情况:哨兵误判。也就是说主库本身没有故障,但由于哨兵的误判,判断它为下线状态。一旦启动主从切换,后续的选举和通知操作都会带来额外的计算和通信开销。因此,为了不必要开销,我们要严格注意误判的情况。在哨兵集群中,判定主库是否处于下线状态,不是由一个哨兵来决定的,而是只有大多数哨兵认为主库已经"主观下线",主库才会标记为"客观下线"。这种判断机制为:少数服从多数。同时会触发主从切换模式。

举个例子,现在有sentinel1、sentinel2、sentinel3三个哨兵和master1一个主库和slave1、slave2、slave3三个从服务器。但sentinel1和sentinel2 判断master1处于上线状态,而sentinel3判断master1处于"主观下线",那么最终master1仍然为上线状态。

简单的来说,"客观下线"的标准为,当有N个实例,最好要有N/2+1个哨兵实例认为其"主观下线",那么主库才是"客观下线"。这样的好处减少了误判的概率,避免了不必要的开销。(当然,有多个实例做出"主线下线"的判断才可以,也可以由Redis管理员自行设定)

1.2.2  redis哨兵机制的选举机制原理

当哨兵开始进行主从切换时,哨兵如何进行选举新的主库呢?它到底遵循什么样的机制?一般来说,我把哨兵选举新主的过程总结为"筛选+排序"。首先,哨兵会按照一定的筛选机制筛选掉不符合要求的从库,然后从符合条件的从库中进行排序,从而诞生出新库。

首先先说筛选机制

  • 筛除掉所有处于下线或者断线状态的从服务器,这可以保证剩余的从服务器都是正常在线的。
  • 筛除掉所有在规定时间内没有响应哨兵的INFO命令的从服务器,这可以保证剩余的从服务器都是最近成功进行通信的。
  • 筛除掉所有与已下线主服务器连接断开超过down-after-milliseconds*10毫秒的从服务器,这样可以保证剩余的从服务器都没有过早地与主服务器断开连接,换句话来说,列表中的从服务器保存的数据都是比较新的。

上述的为筛选机制,接下来排序机制

  • 哨兵会根据从服务器的优先级,对列表中剩余的从服务器进行排序,选出优先级最好的从服务器。
  • 若有多个相同最好优先级的从服务器,那么哨兵会按照复制偏移量对具有相同优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器。
  • 若有多个优先级最高、复制偏移量最大的从服务器,那么哨兵将按照运行ID对这些从服务器进行排序,并选出其中运行ID最小的从服务器。

首先哨兵会筛选掉已下线、断线状态、网络状态不好的从服务器,其次,会根据从服务器优先级、复制偏移量、运行ID方面进行排序,最终得到一个从服务器,那么该从服务器为新的主服务器。

1.3 redis Sentinel 自动发现(基于pub/sub机制的客户端事件通知)

一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性,并进行信息交换。

你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址,因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel。

  • 每个 Sentinel 会以每两秒一次的频率,通过发布与订阅功能,向被它监视的所有主服务器和从服务器的频道发送一条信息,信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。
  • 每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的频道, 查找之前未出现过的 sentinel 。当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中。
  • Sentinel 发送的信息中还包括完整的主服务器当前配置。如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。
  • 在将一个新 Sentinel 添加到监视主服务器的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel。

1.3.1 多个sentinel进行通信

在哨兵集群下,哨兵实例进行通信,是基于Redis提供的pub/sub机制的,也就是发布/订阅模式。在主从集群中,哨兵节点不会直接与其他哨兵节点建立连接,而是首先会和主库建立起连接,然后向一个名为"_sentinel_:hello"频道发送自己的信息(IP+port),其他订阅了该频道的哨兵节点就会获取到该哨兵节点信息,从而哨兵节点之间互知。通俗讲,Redis哨兵模式中,哨兵节点的互通是通过订阅指定的频道来进行的,而不是直接与其他sentinel节点建立起连接。

举个例子,假如现在有sentinel1、sentinel2、sentinel3三个sentinel在监控同一个服务器,那么当sentinel1向主服务器的_sentinel_:hello频道发送一条信息时,所有订阅了_sentinel_:hello频道的sentinel(包含sentinel自己在内)都会收到这条消息。如下图所示:

当一个sentinel从_sentinel_:hello频道收到一条消息后,sentinel会对这条信息进行分析,提取出信息中的sentinel IP地址、sentinel端口号、sentinel运行ID等八个参数,并进行检查:

  • 如果信息中记录的sentinel运行ID和接收信息的sentinel的运行ID相同,那么说明这条消息是sentinel自己发送的,sentinel将丢失这条信息,不做进一步处理。
  • 相反地,如果信息记录的sentinel运行ID和接收信息的sentinel的运行ID不相同,那么说明这条信息是监控同一个服务器的其他sentinel发来的,接收信息的sentinel将根据信息中的各个参数,对相应主服务器的实例结构进行更新。

1.4 Redis通知原理

经过哨兵机制的监控,主动的或者是被动下线了当前master当选举了新的master节点的时候的,经过的redis的自动的切换主库,这个时候需要将新的master节点信息通知到整个的redis节点中,详细的结果如下图所示:

1.5 Redis哨兵机制总结

sentinel只是一个运行在特殊环境下的Redis,不提供数据存储服务。

sentinel会通过向主服务器发送INFO命令获取主服务器所属的从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及向这些从服务器发送命令连接和订阅连接。

在一般情况下,sentinel会以每10s一次的频率向被监视的主库和从库发送INFO命令,获取主库和从库的相关信息。当主库处于下线状态,或者sentinel正对主服务器进行故障转移操作时,sentinel向从服务发送INFO命令的频率修改为每秒一次。

对于监控同一个主服务器的哨兵来说,他们通过向主服务器的_sentinel_:hello发送消息来向其他sentinel告知自己的存在。其他订阅了该频道的sentinel都可以接收到,从而各个sentinel互知。

sentinel只会与主服务器和从服务器之间建立命令连接和订阅连接,而sentinel之间只会建立命令连接,进行通信。

sentinel会以每秒一次的频率向实例(从服务器、主服务器、其他sentinel)发送PING命令,并根据实例对PING命令的回复来判断实例是否在线,当一个实例在指定时间内未响应PING命令,则判定其为主观下线。

在哨兵集群下,当sentinel收到足够多的主观下线投票之后,他会将主服务器判断为客观下线,并发起一个针对主服务器的故障转移操作。

1.6 哨兵机制的缺点

  • 1、当master挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,存在访问瞬断的情况;若在电商网站大促的时候master给挂掉了,几秒钟损失好多订单数据;
  • 2、哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。
  • 3、Redis的单节点内存不能设置过大,若数据过大在主从同步将会很慢;在节点启动的时候,时间特别长;(从节点上有主节点的所有数据)

二、哨兵机制中的问题与解决方案

2.1 主从异步复制导致的数据丢失

redis master 和slave 数据复制是异步的,像前面说的MySQL差不多,这样就有可能会出现部分数据还没有复制到slave中,master就挂掉了,那么这部分的数据就会丢失了。

现在当我们的slave在数据复制的时候,发现返回的ACK时延太长达到了 min-slaves-max-lag 配置,这个时候就会认为如果master宕机就会导致大量数据丢失,所以就提前进行了预测,就不再去接收客户端的任何请求了,来将丢失的数据降低在可控范围内。

min-slaves-to-write 1 # 要求至少一个slave
min-slaves-max-lag 10 # 数据复制和同步的延迟不能超过10s

 2.2 脑裂导致的数据丢失问题

脑裂其实就是网络分区导致的现象,比如,我们的master机器网络突然不正常了发生了网络分区,和其他的slave机器不能正常通信了,但是其实master并没有挂还活着好好的呢,但是哨兵它会认为master宕机,那么问题来了,client可能还在继续写master的,还没来得及更新到新的master呢,那这部分数据就会丢失。

减少脑裂数据的丢失(不能保证完全保证数据不丢失,竟可能的少丢失):

  • 如果master出现了脑裂,和其他的slave失去了通信,不能继续给指定数量的slave发送数据。
  • slave超过10秒没有给自己返回ack消息。
  • master就会拒绝客户端的写请求

三、Redis哨兵机制的实战

博文参考

Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理 - SegmentFault 思否

一文读懂Redis的哨兵机制 - 知乎

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐