说一说Redis的Sentinel模式是如何保证服务器高可用的
我们之前文章说的Redis的主从架构模式实现了读写分离,支持了高并发的业务场景。主从模式也由单台Redis服务器变成了多台Redis服务器,服务器数量一多,当某服务器发生故障宕机的时候,可能就会影响到其它正在工作的服务器,然后产生连锁反应,进而使得整个系统崩溃。对于这种情况我们需要拿出一个方案,使得在某一台或者多台服务器宕机时,要保证不会影响到其它正常工作的服务器,继续维持整个系统正常运转。而Redis的哨兵模式就提供了高可用的解决方案!
哨兵模式,是由一个或者多个哨兵实例组成的哨兵系统,这个系统可以监视多个主服务器和从服务器,并在被监视的主服务器进入下线状态时,自动将某个从服务器升级为新的主服务器,然后由新的主服务器替代原来下线的主服务器继续工作。
当原来的主服务器下线时长超过设置的上限时,Sentinel系统就会执行故障转移操作。Sentinel系统会挑选下线主服务器其中一个从服务器,将它升级为主服务器,然后让其他从服务器成为这升级为新的主服务器的从服务器
如果当原来下线的主服务器又重新上线时,会设置它成为新的主服务器的从服务器
一个Sentinel实例本质上还是一台Redis服务器,只不过它是运行在特殊模式下的服务器,它的主要任务不是存储数据而是监视其它Redis服务器是否还在正常工作,以及发生故障时给出解决方案,保证其它服务器能够继续工作。
Sentinel既然要监视主服务器和从服务器,首先得和主服务器建立连接。Sentinel在与Redis主服务器建立连接的时候,会创建两个异步网络连接。一个是命令连接,一个是订阅连接。为什么会有两个连接呢?因为如果在信息发送时,想要接收信息的服务器不在线或者断线,那么这个服务器就会丢失这个信息。为了不丢失信息,Sentinel必须专门用一个订阅连接来接收信息。同时Sentinel还必须向主服务器发送命令,以此来与主服务器进行通信,所以Sentinel还必须向主服务器创建命令连接。因为Sentinel需要和多个Redis服务器创建网络连接,所以Sentinel使用的是异步连接。
Sentinel与主服务器建立连接之后,就可以获取到主从服务器的信息以及当前状态。Sentinel默认每10秒钟,就会向主服务器发送INFO
命令,并通过主服务器的回复来获取主服务器的当前信息。
通过主服务器回复INFO
命令的相关信息,我们可以获取到从服务器的信息。
当Sentinel发现主服务器有新的从服务器出现时,除了会为这个新的从服务器创建相应的实例结构加入到slaves字典中以外,还会创建连向这个新从服务器的命令连接和订阅连接。
Sentinel在与主从服务器都建立了命令连接和订阅连接以后,Sentinel默认每2秒种通过命令连接,向**_sentinel _:hello
频道发送publish
命令。命令包含了Sentinel自身的信息以及主服务器和从服务器的相关信息。同时还会通过订阅连接向服务器的_sentinel _:hello
频道发送subscribe
**命令。
对于每个Sentinel连接的服务器,Sentinel既通过命令连接向服务器的**_sentinel _:hello频道发送消息**,又通过订阅连接从服务器**_sentinel _:hello频道接收消息**
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被用于更新Sentinel之间的信息,以及更新其它Sentinel监视的服务器的信息。
也就是说,通过_sentinel _:hello频道发送和接收消息,实现了多个Sentinel实例之间以及每个Sentinel所监视的服务器信息的一致性!
通俗点说就是,通过命令连接发送消息,可以使得一个Sentinel让其它Sentinel知道自己的存在。通过订阅连接接收信息,可以使得一个Sentinel通过分析接收到的频道信息来获知其它Sentinel的存在。所以,监视同一个主服务器的多个Sentinel可以自动发现对方。当一个Sentinel发现另一个Sentinel时,它会更新自己的sentinels字典,并且还会和这个新Sentinel互相创建命令连接,但不会创建订阅连接。
当Sentinel与主从服务器之间、Sentinel与其它Sentinel之间都建立了命令连接之后,Sentinel默认每1秒钟向所有建立了命令连接的主从服务器,以及其它Sentinel发送PING命令,并通过其它服务器返回的PING命令的回复,来判断对方是否在线。
Sentinel配置文件中的down-after-milliseconds
选项指定了Sentinel判断实例进入主观下线所需的时间长度。超过这个时间长度,实例仍然没有向Sentinel发送有效回复的话,Setinel就判定该实例已经进入主观下线状态。
当Sentinel将一个主服务器判定为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一个主服务器的其它Sentinel进行询问,看他们是否也认为这个主服务器已经进入了下线状态。当Sentinel从其它Sentinel那里接受到足够数量的已下线判断之后,Sentinel就会将主服务器判定为客观下线,并对主服务器进行故障转移操作。
我们说过当一个Sentinel认为某个主服务器下线了还不能确定这个主服务器是真正下线了,还需要其它Sentinel来判断,当大多数Sentinel都认为这个主服务器下线,那才是真正的下线了。当一个主服务器被判定为客观下线时,监视这个主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由这个领头Sentinel对下线主服务器执行故障转移操作。
所有在线的Sentinel都有被选为领头Sentinel的可能,每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel,设置局部领头Sentinel的规则是先到先得。如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel就成为了领头Sentinel
在选举出领头的Sentinel之后,这个领头Sentinel将对已经下线的主服务器执行故障转移操作。
故障转移主要分为三个步骤:
- 在已经下线的主服务器的所有从服务器中,挑选出来一个从服务器,并将其转换为主服务器
- 让已经下线的主服务器的所有从服务器改为复制新的主服务器
- 将已下线的主服务器设置为新的主服务器的从服务器,当这个下线的主服务器重新上线时,就会成为新的主服务器的从服务器
那么是如何选出新的主服务器来的呢?
领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照选举规则进行选举:
- 删除列表中所有处于下线或者断线状态的从服务器,保证列表中剩余的从服务器都是正常工作的
- 删除列表中所有最近5秒内没有回复过领头Sentinel的INFO命令的从服务器,保证列表中剩余的从服务器都是最近成功进行过通信的
- 删除所有与已下线主服务器连接断开超过
down-after-milliseconds
* 10 时长的从服务器。down-after-milliseconds
选项执行了判断主服务器主观下线所需要的时间,删除断开10倍时长的从服务器是为了保证列表中从服务器没有过早的与主服务器断开连接,即保证剩余的从服务器保存的数据都是比较新的。
删除以上这些不符合要求的从服务器以后,领头Sentinel将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出优先级最高的从服务器。如果有多个具有相同优先级的从服务器,那么领头Sentinel将按照从服务器的复制偏移量,再进行排序,并选出其中复制偏移量最大的从服务器,复制偏移量最大也就意味着这个从服务器是与原来已下线的主服务器最为同步的从服务器,也就是保存着最新的数据的从服务器。如果说优先级、复制偏移量都一样,那么领头Sentinel将按照运行ID对这些从服务器再进行排序,选出运行ID最小的从服务器。
选出新的主服务器以后,领头Sentinel向选中的从服务器发送salveof on one
命令接触这个从服务器和原来主服务器的主从关系。
发送salveof on one
命令之后,领头Sentinel会每1秒钟向它发送INFO
命令,来获取这个选出来的从服务器的信息。当这个从服务器的role角色信息由slave
变成master
时,说明这个被选中的从服务器已经升级为主服务器了。
在选出从服务器并成为新的主服务器之后,我们还需要修改其它的从服务器复制目标,让其它从服务器去复制新的主服务器。
在选出新的主服务器,并且将其它从服务器复制目标修改为新的主服务器之后,最后一步是把旧的主服务器变为新的主服务器的从服务器。当旧的主服务器重新连接时,就会成为新的主服务器的从服务器。
至此,就完成了故障转移操作。
从上面对Sentinel模式讲解可以看出,Sentinel哨兵模式为主从架构模式提供了高可用支持。在主服务器发生故障下线之后,进行故障转移,保证服务器能够正常继续工作。