哨兵 Sentinel
哨兵是一个运行在特殊模式下的Redis进程,主要负责三个任务:
- 监控
- 选主(选择主库)
- 通知
哨兵集群
为了避免单个哨兵故障后无法进行主从切换,以及为了减少误判,引入了哨兵集群。
怎麽监控?
哨兵周期性地给所有主从库发送PING命令,检测它们是否在线。如果从库没有在规定时间内响应哨兵的PING命令,哨兵就会把它们标记为“下线状态”。如果主库没有在规定时间内响应哨兵的PING命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。
怎麽判定主库真的挂了?
如果检测的是从库,那么,哨兵简单地把它标记为“主观下线”就行了,因为从库的下线影响一般不太大,集群的对外服务不会间断。
如果检测的是主库,哨兵不能简单地把它标记为“主观下线”,开启主从切换。因为有可能是哨兵误判了,其实主库并没有故障。可是,一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销。
为了避免误判,引入哨兵集群。在判断主库是否下线时,不能由一个哨兵说了算,只有大多数的哨兵实例,都判断主库已经“主观下线”了,主库才会被标记为“客观下线”,这个叫法也是表明主库下线成为一个客观事实了。判断原则是:少数服从多数(大于等于quorum 配置项)。同时,这会进一步触发哨兵开始主从切换流程。
哨兵主节点挂了怎么办?
分布式系统容错问题,这方面最著名的就是分布式领域中的“拜占庭将军”问题。
存在故障节点时,只要集群中大多数节点状态正常,集群依旧可以对外提供服务。
基于 pub/sub 机制的哨兵集群组成
在配置哨兵的信息时,我们只需要用到下面的这个配置项,设置主库的 IP 和端口,并没有配置其他哨兵的连接信息
sentinel monitor <master-name> <ip> <redis-port> <quorum>
这些哨兵实例既然都不知道彼此的地址,又是怎么组成集群的呢?
哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制。哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如说发布它自己的连接信息(IP 和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息。在主从集群中,主库上有一个名为__sentinel__:hello的频道,不同哨兵就是通过它来相互发现,实现互相通信的。
哨兵怎麽知道从库的IP地址和端口?
哨兵默认以每10秒一次的频率,向主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。接着,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控。
怎麽把新主库的信息通知给从库和客户端?
基于pub/sub机制的客户端事件通知
每个哨兵实例都提供pub/sub机制,客户端可以从哨兵订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过程中的不同关键事件,例如:主库下线判断、新主库选定、从库重新配置等。
客户端读取哨兵的配置文件后,可以获得哨兵的地址和端口,和哨兵建立网络连接。然后,我们可以在客户端执行订阅命令,来获取不同的事件消息。
当哨兵把新主库选择出来后,客户端就会看到switch-master事件。这个事件表示主库已经切换了,新主库的 IP 地址和端口信息已经有了。这个时候,客户端就可以用这里面的新主库地址和端口进行通信了。
客户端主动获取
如果客户端因为某些原因错过了哨兵的通知,或者哨兵通知后客户端处理失败了,安全起见,客户端最好主动去获取最新主从的地址进行访问。
所以,客户端需要访问主从库时,不能直接写死主从库的地址了,而是需要从哨兵集群中获取最新的地址(sentinel get-master-addr-by-name命令),这样当实例异常时,哨兵切换后或者客户端断开重连,都可以从哨兵集群中拿到最新的实例地址。
实现
无论是哨兵主动通知客户端,还是客户端主动去获取,代码都是需要自己实现的。但是,一般Redis的SDK都提供了通过哨兵拿到实例地址,再访问实例的方式,我们直接使用即可,不需要自己实现这些逻辑。
由哪个哨兵执行主从切换?
判断主库为客观下线的哨兵节点,发起“Leader选举”,表明希望由自己来执行主从切换。成为Leader要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。
哨兵选择哪个从库作为新主库?
过滤
- 已经下线的从库不选
- 之前网络连接不好的从库不选:配置项down-after-milliseconds,down-after-milliseconds毫秒内,主从节点依然没有连接上,这种情况认定为主从节点断连。如果断连发生次数超过10次,就可以认定这个从库的网络连接状态不好。
打分
- 先按照优先级来选择,选择优先级最高的从库。配置项slave-priority
- 如果优先级一样,再看看哪个从库和旧主库同步程度最接近
- 如果上述两个都一样,那就选择实例ID号小的从库