上一篇介绍了 Redis 的主从模式。这节开始介绍 Redis 的哨兵机制。
上一节提到的,主从架构可以在主节点发生故障时,将从节点切换为主节点继续使用,从而实现整个集群的高可用性。
Redis2.8 以后提供了哨兵(Sentinel)机制,它的作用是自动进行主从节点异常下线和故障转移。它会监测节点是否存活,如果发现节点挂了,就会将节点下线;如果故障的是主节点,还会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。
哨兵其实是一个运行在特殊模式下的 Redis 进程。
如果只是单个哨兵节点来监测并进行故障转移,可能会由于网络原因误判节点下线,从而造成不必要的开销。而且单个哨兵节点也不能够保证可靠性,所以有必要使用多个节点,组成哨兵集群来完成工作。
哨兵节点之间是通过发布/订阅机制来相互发现的。
在主从集群中,主节点上有一个名为 __sentinel__:hello 的频道,不同哨兵就是通过它来相互发现,实现互相通信的。
哨兵节点接入集群后,会以每 10 秒一次的频率向主节点发送 INFO 命令来获取所有从节点的信息,从而与每个从节点建立连接并监控。
哨兵实例越多,误判率就越低,但是哨兵集群中的实例不是越多越好,因为哨兵实例越多,通信的次数和耗时也就越多,会影响到哨兵的通信和选举。
哨兵节点主要负责三件事情:监控、故障转移、通知。
当哨兵检测到一个实例下线时,它会自动发出警报。哨兵发出的警报可以通过以下两种方式向管理员进行通知:
哨兵会每隔 1 秒给所有主从节点和其他哨兵节点发送 PING 命令。当节点收到 PING 命令后,会发送一个 PONG 响应命令给哨兵,这样就可以判断它们是否在正常运行。
哨兵节点也是一个 Redis 实例,如果检测出哨兵集群中的某个实例挂了,会对其进行主观下线操作。
如果主节点或者从节点没有在规定的时间内(配置项中的 down-after-milliseconds,单位毫秒)响应哨兵的 PING 命令,或者返回的状态信息表示节点已经下线,哨兵就会将它们标记为「主观下线」。
可以通过 down-after-milliseconds 参数配置故障转移的敏感度:
PING 命令的作用是检测节点是否正常工作;INFO 命令是获取节点的角色和连接信息。
主节点没有响应并不一定是故障,可能是因为主节点的系统压力比较大或者网络发送了拥塞,导致主节点没有在规定时间内响应哨兵的 PING 命令。
所以,为了减少误判的情况,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,可以降低误判率。
当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,
SENTINEL is-master-down-by-addr
其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,投出赞成票或反对票。
当这个哨兵的赞成票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。
quorum 的值一般设置为哨兵个数的二分之一加 1,例如 3 个哨兵就设置 2。
判断完主库下线后,需要选举出一个哨兵节点来执行主从切换。
候选者:哪个哨兵节点判断主节点为「客观下线」,哪个哨兵节点就是候选者。
候选者会向其他哨兵发送命令,表明希望成为 Leader 来执行主从切换,并让所有其他哨兵对它进行投票。每个哨兵只有一次投票机会,可以投给自己或投给别人,但是只有候选者才能把票投给自己。
那么在投票过程中,任何一个「候选者」,要满足两个条件:
判定主节点“客观下线”不代表可以完成主从切换,因为可能选不出哨兵来执行。
比如 5 个哨兵 ,quorum 设置为 2,如果有 3 个哨兵故障,此时哨兵集群还是可以判定主节点为“客观下线”,但是不能完成主从切换了,因为拿不到半数以上的赞成票。
quorum 的值建议设置为哨兵个数的二分之一加1,而哨兵节点的数量应该是奇数。
如果一轮投票选举没有产生 Leader,哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的 2 倍),再重新选举。这是因为,哨兵集群能够进行成功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞,就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进行投票选举,成功的概率就会增加。
如果一直超时选举不出 Leader 节点进行主从切换,则整个集群会无法正常工作,需要手动处理。
故障转移将由选举出的 Leader 哨兵节点来执行,主从故障转移操作包含以下四个步骤:
首先把已经下线的从节点过滤掉,然后把以往网络连接状态不好的从节点也给过滤掉:如果发生断连的次数超过了 10 次,就说明这个从节点的网络状况不好,不适合作为新主节点。
接下来要对剩余的所有从节点进行三轮考察:优先级、复制进度、ID 号。在进行每一轮考察的时候,哪个从节点优先胜出,就选择其作为新主节点;如果有多个节点情况相同,则进入下一轮考察:
Redis 有个叫 slave-priority 配置项,可以手动给从节点设置优先级。
可以根据服务器性能配置来设置从节点的优先级。比如,如果 「 A 从节点」的物理内存是所有从节点中最大的, 那么我们可以把「 A 从节点」的优先级设置成最高。
如果在第一轮考察中,发现优先级最高的从节点有多个,那么就会进行第二轮考察,比较多个从节点的复制进度。
主节点会用 master_repl_offset 记录当前的最新写操作在 repl_backlog_buffer 中的位置,从节点会用 slave_repl_offset 记录当前的复制进度。从节点的复制进度越靠前,则表示与旧主节点的同步程度越高,进行故障转移后丢失的数据就越少。
如果在第二轮考察中,发现有多个从节点复制进度都是一样的,那么就会进行第三轮考察,比较多个从节点的 ID 号,小的从节点胜出。
每个从节点都有一个编号,这个编号就是 ID 号,是用来唯一标识从节点的。
在选举出从节点后,哨兵 Leader 向被选中的从节点发送 SLAVEOF no one 命令,让这个从节点解除从节点的身份,将其变为新主节点。
在发送 SLAVEOF no one 命令之后,哨兵 Leader 会以每秒一次的频率向被升级的从节点发送 INFO 命令(没进行故障转移之前,INFO 命令的频率是每 10 秒一次),并观察命令回复中的角色信息。当被升级节点的角色信息从原来的 slave 变为 master 时,哨兵 Leader 就知道被选中的从节点已经顺利升级为主节点了。
哨兵 Leader 向已下线主节点属下的所有「从节点」发送 SLAVEOF 命令,让它们指向「新主节点」。
从节点收到命令后,会进行以下操作:
Redis 并没有设置机制保证指向新节点这个过程的可靠性,也就是说,可能由于网络原因,从节点并没有接收到 SLAVEOF,或者在指向新的主节点时出错等情况,导致从节点仍然指向旧的主节点。此时,如果旧的主节点已经无法工作,就会导致从节点也不会继续工作;而如果旧的主节点可以继续工作,就会出现脑裂问题。出现这些问题后,都需要手动解决。
每个哨兵节点提供发布/订阅机制,客户端可以从哨兵订阅消息。
哨兵提供的消息订阅频道有很多,不同频道包含了主从节点切换过程中的不同关键事件,几个常见的事件如下:

客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了。
继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点。
在主从切换的过程中,如果主从集群是采用读写分离模式,当主库故障后,客户端仍然可以把读请求发送给从库,让从库服务。但是,对于写请求操作,就无法执行了。
如果需要客户端不感知服务的中断,需要缓存客户端的写请求,在主从切换完成后,再将请求写入到集群中。但是这个方法存在问题:缓存的写请求没有持久化,如果发生了崩溃,数据并没有写入成功,但是之前却已经返回给客户端成功响应;且对于实时性高的写操作没有办法。
在主从切换的过程中,如果主节点崩溃了,且主从节点之间的数据不同步,哨兵节点仍然会进行主从切换,所以会造成部分数据丢失。
如果新选出的主节点崩溃了,在切换过程中哨兵节点能够检测到,并重新选择一个新主节点;如果是切换完成,则会重新触发一次主从切换。
如果 Leader 哨兵节点崩溃,会重新选举新的 Leader 哨兵(除非节点不足),再进行切换。
如果主库突然出现暂时性 “失联”,而并不是真的发生了故障,此时监听的哨兵会自动启动主从切换机制。当原主库从假故障中恢复后,又开始处理请求,但是哨兵已经选出了新的主库,这样一来,旧的主库和新主库就会同时存在,这就是脑裂现象。
脑裂问题通常存在于主从切换过程中,但是仍然可能存在于切换完成后(如果客户端没有收到哨兵发来的切换主节点的信息,且旧主节点没有收到哨兵发来的 SLAVEOF 更换为从节点的信息)。
脑裂问题可能会导致数据丢失,因为旧主节点仍然在正常处理客户端信息,但是这部分信息没有同步到新主节点和从节点上。
Redis 通过以下两个配置来解决脑裂问题:
这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。
本文介绍了 Redis 的哨兵机制。通过哨兵集群,可以保证主从架构的高可用性,可以实现自动将故障节点下线,并且当主节点故障时,执行主从切换来保证集群的高可用性。为了减少误判,对于非主节点采用主观下线,对于主节点采用客观下线的方式。在故障转移前,需要选举出一个 Leader 哨兵节点来执行。故障转移完成后,会将所有从节点指向新的主节点,并将原先的主节点设置为从节点,并通知客户端主节点的变更。
为了保证故障转移的快速完成,哨兵节点并没有同步等待所有命令被成功执行,所以可能出现一些问题,如脑裂问题等。
下一节将介绍 Redis 的切片集群。