上节课,学习了主从库集群模式。
在主从库集群模式下,如果从库发生故障了,客户端可以继续向主库或其他从库发送请求,进行相关的操作,
但是如果主库发生故障了,那就直接会影响到从库的同步,因为从库没有相应的主库可以进行数据复制操作了。
而且,如果客户端发送的都是读操作请求,那还可以由从库继续提供服务,这在纯读的业务场景下还能被接受。但是,一旦有写操作请求了,按照主从库模式下的读写分离要求,需要由主库来完成写操作。
如果主库挂了,没有实例可以来服务客户端的写操作请求了。
无论是写服务中断,还是从库无法进行数据同步,都是不能接受的。
所以,如果主库挂了,我们就需要运行一个新主库,比如:把一个从库切换为主库,把它当成主库。
从库切换为主库,这就涉及到三个问题:
- 旧主库真的挂了吗?
- 该选择哪个从库作为新主库?
- 怎么把新主库的相关信息通知给从库和客户端呢?
这就要提到哨兵机制了。
在 Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,
哨兵机制有效地解决了主从复制模式下故障转移的这三个问题。
哨兵机制的基本流程
哨兵是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,哨兵进程也在运行。
哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
监控是指:哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令,检测主从库是否仍然在线运行。
如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“下线状态”;
如果主库没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。
自动切换主库流程首先是执行哨兵的第二个任务:选主。
主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选出一个从库实例,把它作为新的主库。
这一步完成后,现在的集群里就有了新主库。
哨兵会执行最后一个任务:通知。
在执行通知任务时,哨兵会把新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。
同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。
总结来说,有两个通知
- 哨兵通知其他从库,把新主库信息发送给其他从库,让其他从库执行 replacaof 命令,切换主库
- 哨兵通知客户端,把新主库信息发送给客户端,让客户端和新主库建立连接
在监控 和 选主任务中,哨兵需要做出决策:
- 在监控任务中,哨兵需要判断主库是否处于下线状态
- 在选主任务中,哨兵需要选择哪个从库实例作为主库
如何判断主库的下线状态
接下来,我们就先说说如何判断主库的下线状态。
哨兵对主库的下线判断有“主观下线”和“客观下线”两种。
为什么会存在这两种下线判断呢?
这两种下线判断的区别和联系是什么呢?
什么是“主观下线”?
哨兵进程会使用 PING 命令检测 它自己 和主、从库的网络连接情况,用来判断实例的状态。
如果哨兵发现实例对 PING 命令的响应超时了,哨兵就会先把实例标记为“主观下线”。
即:只有本哨兵进程认为该实例下线了,有可能是本哨兵进程的误判,实际上该实例处于在线状态。
如果检测的是从库,哨兵简单地把从库标记为“主观下线”就行了,
因为从库的下线影响一般不太大,集群的对外服务不会间断。
但是,如果检测的是主库,哨兵还不能简单地把主库标记为“主观下线”,不能开始自动切换主库的流程。
因为很有可能存在一个情况:哨兵误判,实际上主库并没有故障,仍然处于在线状态。
一旦开始自动切换主库的流程,选主 和 通知操作都会带来额外的计算和通信开销。
为了避免这些不必要的开销,我们要特别注意误判的情况。
首先,我们要知道什么叫误判。
误判就是:主库实际并没有下线,但是哨兵误以为该主库下线了。
误判一般会发生在集群网络压力较大、网络拥塞,或者是主库本身压力较大的情况下。
一旦哨兵判断主库下线了,就会开始选择新主库,并让从库和新主库进行数据同步,这个过程本身就会有开销。
例如,哨兵要花时间选出新主库,从库也需要花时间和新主库同步。
而在误判的情况下,主库是不需要进行切换的,所以这个过程花费的开销是没有价值的。
正因为这样,我们需要判断是否有误判,以及减少误判。
那怎么减少误判呢?(哨兵集群)
在日常生活中,当我们要对一些重要的事情做判断的时候,经常会和家人一起商量,然后再做决定。
哨兵机制类似,它通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。
引入多个哨兵实例一起判断,就可以避免单个哨兵,因为自身网络状况不好,而误判主库下线的情况。
多个哨兵实例的网络同时不稳定的概率较小,误判率也能降低。
在判断主库是否下线时,不能由一个哨兵说了算,只有大多数的哨兵判断主库已经“主观下线”了,主库才会被标记为“客观下线”,这个叫法也是表明:主库下线成为了一个客观的事实。
这个下线判断原则就是:少数服从多数。
简单来说,“客观下线”的标准就是:当有 N 个哨兵实例时,最好要有 (N / 2 + 1) 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。
这样一来,就可以减少误判的概率,也能避免误判带来的无用的主从库切换。
有多少个实例做出“主观下线”的判断,才能最终判定主库为“客观下线”,可以由 Redis 管理员自行设定。
如何选定新主库
一般来说,我把哨兵选择新主库的过程称为“筛选 + 打分”。
简单来说,我们在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。
然后,我们再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库,如下图所示:
筛选
在上面那段话里,需要注意的是两个“一定”,现在,我们要考虑这里的“一定”具体是指什么。
一定的筛选条件。
一般情况下,我们要先保证所选的从库仍然在线运行。
但是,从库正常在线,这只能表示从库的现状良好,并不代表该从库就是最适合做主库的。
设想一下,一个从库正常运行,我们把该从库选为新主库开始使用了。
可是,很快该从库的网络出了故障,那么,我们就得重新选新主库了。
这显然不是我们期望的结果。
所以,在选择新主库时,除了要检查从库的当前在线状态,还要判断该从库之前的网络连接状态。
如果该从库总是和主库网络断连,而且断连次数超出了一定的阈值,
我们就有理由相信,该从库的网络状况并不是太好,就可以把该从库筛掉了。
具体怎么判断从库之前的网络连接状态呢?
使用配置项 down-after-milliseconds * 10。
如果哨兵实例发现对 PING 命令的响应超时了,哨兵就会先把实例标记为“主观下线”。
down-after-milliseconds 毫秒,就是响应最大超时时间。超过这个毫秒数,就代表响应超时。
如果响应超时的次数超过了 10 次,就说明这个从库的网络状况不好,不适合作为新主库。
打分
接下来就要给剩余的从库打分了。
我们可以分别按照三个规则依次给剩余的从库进行三轮打分,
这三个规则分别是:从库优先级、从库复制进度 以及 从库 ID 号。
只要在某一轮中,有从库得分最高,那么它就是主新库了,选主过程到此结束。
如果没有出现得分最高的从库,那么就继续按照下一个规则给剩余的从库进行打分。
第一轮:优先级高的从库得分高。
用户可以通过 slave-priority 配置项,给不同的从库设置不同优先级。
比如,你有两个从库,它们的内存大小不一样,可以手动给内存大的实例设置一个高优先级。
在选主时,哨兵会给优先级高的从库打高分,如果有一个从库优先级最高,那么它就是新主库了。
如果从库的优先级都一样,那么哨兵开始第二轮打分。
第二轮:和旧主库的数据同步进度最接近的从库得分高。
这个规则的依据是:如果选择和旧主库的数据同步进度最接近的那个从库作为新主库,这个新主库上就有最新的数据。
如何判断从库和旧主库间的数据同步进度呢?
主从库数据同步时有个命令传播的过程。
在命令传播的过程中,主库会用 master_repl_offset 记录当前的最新写操作在 repl_backlog_buffer 中的位置,而从库会用 slave_repl_offset 这个值记录当前的复制进度。
我们想要找的从库,它的 slave_repl_offset 需要最接近 master_repl_offset。
第三轮:ID 号小的从库得分高。
每个实例都会有一个 ID,这个 ID 就类似于从库的编号。
Redis 在选主库时,有一个默认的规定:在优先级和复制进度都相同的情况下,ID 号最小的从库得分最高,ID 号最小的从库会被选为新主库。
选定新主库的整体流程
首先,哨兵会按照在线状态、网络状态,筛选过滤掉一部分不符合要求的从库,
然后,依次按照优先级、复制进度、ID 号大小再对剩余的从库进行打分,得分最高的从库,就把它选为新主库。
如果在第一轮中出现了优先级最高的从库,且没有并列,那么选主过程就结束了。
小结
这节课,我们学习了哨兵机制,哨兵机制是实现 Redis 不间断服务的重要保证。
具体来说,主从集群的数据同步,是数据可靠的基础保证;
而在主库发生故障时,自动的主从切换是服务不间断的关键支撑。
Redis 的哨兵机制自动完成了以下三大功能,从而实现了主从库的自动切换,可以降低 Redis 集群的运维开销:
- 监控主库运行状态,并判断主库是否客观下线
- 在主库客观下线后,选取新主库
- 选出新主库后,通知从库 和 客户端
为了降低误判率,在实际应用时,哨兵机制通常采用多实例的方式进行部署,
多个哨兵实例通过“少数服从多数”的原则,来判断主库是否客观下线。
一般来说,我们可以部署三个哨兵,如果有两个哨兵认定主库“主观下线”,就可以开始自动切换主库的流程。
如果你希望进一步提升判断准确率,也可以再适当增加哨兵个数,比如:使用五个哨兵。(保证数量为奇数个)
但是,使用多个哨兵实例来降低误判率,其实相当于组成了一个哨兵集群,
我们会因此面临着一些新的挑战,例如:
- 哨兵集群中有实例挂了,怎么办,会影响主库状态判断和选主吗?
- 哨兵集群多数实例达成共识,判断出主库“客观下线”后,由哪个实例来执行主从切换呢?
每课一问
这节课,我提到,通过哨兵机制,可以实现主从库的自动切换,这是实现服务不间断的关键支撑,
同时,我也提到了主从库切换是需要一定时间的。
所以,请你考虑下,在这个切换过程中,客户端能否正常地进行请求操作呢?
如果想要应用程序不感知服务的中断,还需要哨兵或需要客户端再做些什么吗?
问题1
主从集群一般是采用读写分离模式,当主库故障后,客户端仍然可以把读请求发送给从库,让从库服务。
但是,对于写请求操作,客户端就无法执行了。
问题2
一方面,客户端需要能缓存应用发送的写请求。
只要不是同步写操作(Redis 应用场景一般也没有同步写),写请求通常不会在应用程序的关键路径上,所以,客户端缓存写请求后,给应用程序返回一个确认就行。
另一方面,主从切换完成后,客户端要能和新主库重新建立连接,哨兵需要提供订阅频道,让客户端能够订阅到新主库的信息。
同时,客户端也需要能主动和哨兵通信,询问新主库的信息。