上一篇文章介绍了高可靠方案:主从集群模式。通过主从库的读写分离,来保证服务的可靠性。
当某个从库出现故障时,不影响服务的使用,主库仍然可以处理写命令,其他从库可以处理读命令。但主库发生故障,就不能处理写命令了,从库只能处理读命令。这就影响服务的正常使用了,该如何解决呢?
只要找一个从库当主库就可以解决了。但还有三个问题需要处理:
- 主库真的挂了吗?
- 该选择哪个从库作为主库?
- 怎么把新主库的相关信息通知给从库和客户端?
这里就要介绍Redis的哨兵机制了。哨兵机制指在Redis主从集群模式下,实现主从库自动切换。它有效地解决了主从集群模式下故障时的三个问题。
哨兵机制的基本流程
哨兵其实是一个运行在特殊模式下的Redis进行,主从库实例运行的同时,它也在运行。哨兵主要负责的任务有三个:监牢、选主(选择主库)和通知。如下图所示:
- 监控:哨兵进程周期性地给所有主从库发送PING命令,检测它们是否在线运行
- 选主(选择主库):选出新主库
- 通知:让从库执行replicaof,与新主库同步;通知客户端,与新主库连接。
通知任务相对比较简单且容易理解,但在监控和选主这两个任务中,哨兵需要做出两个决策:
- 在监控任务中,哨兵需要判断主库是否处于下线状态;
- 在选主任务中,哨兵要决定选择哪个从库实例作为主库。
下面就介绍一下这两个任务。
哨兵是如何判断主库是否下线
哨兵对主库的下线状态判断有“主观下线”和“客观下线”两种。
- 主线下线,哨兵发现主库或从库对PING命令的响应超时。
- 客观下线,表示主库下线是一个客观事实。
哨兵在判断主从库采用不同方式:
- 哨兵PING从库,如果超时,就直接标记“主观下线”
- 哨兵PING主库,超时不能直接标记“主观下线”,因为可能由于网络阻塞等原因导致误判。
如何解决哨兵误判?
通过哨兵集群,也就是由多个哨兵组成的集群来进行判断。采用少数服从多数,超过N/2+1判断主库为“主观下线”,那就判断主库为“客观下线”。关于哨兵集群,下面会详细介绍。
哨兵是如何选定新主库的?
哨兵选定新主库用四个字概括:“筛选+打分”。简单来说,就是根据筛选条件选出候选从库,然后通过打分,选出最高分的作为新主库。下面说一下筛选条件和打分规则。
筛选条件
首先从库必须在线运行。
其次从库网络状态良好。从库和主库断连超出一定的阈值就把这个从库筛掉。这个阈值就是down-after-milliseconds
,表示主从库断连的最大连接超时时间。例如发生超过10次,就认定从库的网络状况不好。
关于down-after-milliseconds
,值越小,哨兵就越敏感。当网络拥塞但主库正常,可能会发生不必要的切换。而当主库真的故障了,就切换及时,对业务影响最小。
因此down-after-milliseconds
要设置合适的值,既减少不必要的切换,也保证能够及时切换,降低对业务的影响。
打分规则
第一轮,优先级最高的从库得分高。可通过slave-priority
配置项,设置从库优先级。
第二轮,和旧主库同步程度最接近的从库得分高。很容易理解,越接近主库,说明数据丢失越少。前面介绍主从复制时,已经知道主从库是通过repl_backlog_buffer
保持同步的,所以slave_repl_offset
最接近master_repl_offset
,得分高。
第三轮,ID号小的从库得分高。每个实例都会有一个ID。这是最后一轮,必须决出胜负,所以就选择最小ID的作为新主库。
到这里,我们对哨兵机制的基本流程有了一个整体的认识,下面我们再来了解关于主从切换的两个问题。
在做主从切换时,客户端能否正常地进行请求操作?
如果客户端使用了读写分离,那么读请求不受影响,而写请求会失败。失败持续时间 = 哨兵切换主从的时间 + 客户端感知到新主库的时间。
如果不想让业务感知到异常,那客户端只能把写失败的请求先缓存起来,或者写入消息队列中间件。这种只适合对写入请求返回值不敏感的业务。
应用程序不感知服务中断,哨兵和客户端要做什么?
当哨兵完成主从切换后,客户端需要及时感知到主库发生了变更,然后把缓存的写请求写入到新库中,保证后续写请求不会再受到影响。具体做法有两方面:
一方面是哨兵主要通知客户端。哨兵在选主后,会把新主库的地址写入自己实例的pub/sub里,客户端需要订阅pub/sub。当切换新主库后,客户端就能拿到新主库的地址,把写请求发到这个新主库即可。
另一方面是客户端主动获取最新的主从地址。如果客户端因为某些原因错过了哨兵的通知,或者哨兵通知后客户端处理失败了,就需要客户端主动获取。
下面再来学习哨兵集群。
哨兵集群
如果哨兵实例在运行时发生故障,主从库还能正常切换吗?
通常,我们在解决一个系统问题的时候,会引入一个新机制,或者设计一层新功能。这里Redis引入哨兵集群来解决哨兵实例的高可靠性问题。
基于pub/sub机制组成哨兵集群
哨兵实例之间可以互相发现,靠的是Redis提供的pub/sub机制,也就是发布/订阅机制。
哨兵和主库建立连接,就可以在主库上发布消息了,比如发布自己的连接信息(IP和端口),同时它也会从主库上订阅消息,获得其他哨兵的连接信息。
当多个哨兵实例都在主库上做了发布和订阅操作,它们之间就能知道彼此的IP地址和端口了。
哨兵除了彼此之间建立起连接形成集群外,还需要和从库建立连接。因为在哨兵的监控任务中,它需要对主从库都进行监控,而且在主从库切换完成后,还需要通知从库,让它们和新主库进行同步。
那么,哨兵是如何知道从库的IP地址和端口的呢?
哨兵通过向主库发送INFO命令来完成的。如下图所示:
通过pub/sub机制,哨兵之间可以组成集群;通过INFO命令,哨兵和从库建立连接,并进行监控。
但是哨兵不能只和主、从库连接。因为,主从库切换后,客户端也需要知道新主库的连接信息。所以哨兵还需要把新主库的信息告诉客户端。
还是可以依赖pub/sub机制来完成哨兵和客户端的信息同步。
基于pub/sub机制的客户端事件通知
本质上,哨兵就是一个运行在特定模式下的Redis实例,只完成监控、选主和通知的任务,因此它也具有pub/sub功能。下面是哨兵提供的一些重要的频道。
知道这些频道后,可以让客户端从哨兵这里订阅消息了。
由哪个哨兵执行主从切换?
在哨兵集群模式下,通过“投标仲裁”,选出哨兵Leader来执行主从切换。投标仲裁的流程如下图所示:
任何一个实例判断主库“主观下线”,就会给其他实例发送is-master-down-by-addr命令。
其他实例会根据自己和主库的连接情况,做出Y或N响应。
一个哨兵获得仲裁所需的赞同票数后,就可以标记主库为“客观下线”。这个票数可以通过哨兵配置文件中的quorum配置项来设定。
再给其他哨兵发送命令,表示希望由自己来执行主从切换,并让其他哨兵进行投票。这个也叫“Leader选举”,满足以下两个条件才能当Leader:
- 拿到半数以上的票数
- 拿到的票数>=quorum
小结
- 哨兵机制是实现Redis不间断服务的保证。
- 哨兵机制的三大任务:监控、选主、通知。
- 为了降低误判率,通过采用哨兵集群,并采用“少数服从多数”的原则,判断主库是否客观下线。
- 哨兵集群的关键机制,包括:
- 基于pub/sub机制的哨兵集群组成过程;
- 基于INFO命令的从库列表,这可以帮助哨兵和从库建立连接;
- 基于哨兵自身的pub/sub功能,实现了客户端的哨兵之间的事件通知。
- 哨兵集群在判断了主库“客观下线”后,经过投票仲裁,选举一个Leader来负责主从切换。
最后再分分享一个经验:要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值down-after-milliseconds。