1.概述
redis主从复制解决了单机下数据丢失的问题,在一主多从的结构下,倘若master出现故障,slaves仍然有数据备份。此时一个自然而然的想法就涌现出来了:在master不可用的情况下,能否重新调整主从关系,从slave中选取一个服务器作为新的master,从而实现redis的高可用。sentinel就是这样一种解决方案:由一组哨兵(sentinel)实例组成一个sentinel系统,该系统保证其自身高可用性的同时,监控redis主服务器和该主服务器所属的从服务器,当主服务器下线时长超过阈值之后,由sentinel系统挑选一个从服务器,将该服务器升级为新的主服务器,完成故障转移。同时,sentinel还持续监控已下线的服务器,当他重新上线时,将该服务器设为新的master下的slave。
sentinel系统的运行流程如图:
-
假设现有一个master和3各slave。通过配置sentinel系统,使其监控这些服务器
-
当master服务器(server1)下线, 主从复制将会被终止。同时,sentinel感知到server1下线。
3.master下线时间超过阈值,sentinel系统开始故障转移。在slave中选取一个服务器(如server2),作为新的master,让其他的服务器作为server2的从服务器。另一边,sentinel持续监控server1的状态。
-
当server1重新上线时,sentinel通过SLAVEOF命令将其设置为server2的从服务器。
2.sentinel启动运行全流程
2.1启动sentinel
创建一个类型为sentinel的redis服务器。它本质上仍然是一个redis服务器,但其工作方式和普通redis服务器不同,主要体现在它需要维护各master和master下slave的在线信息。
1.1. 替换服务器代码为Sentinel专用代码Redis_Server_Port --> Redis_Sentinel_Port; redisCommandTable --> sentinelcmds。
1.2. 由服务器初始化Sentinel状态,创建一个sentinelState结构,主要维护了一个保存masters信息的字典。
1.5. 服务器读取sentinel.conf文件,完成Sentinel中masters属性的配置。假设在配置文件中配置了master1和master2,那么masters的状态如图。
1.4. 创建与各master的连接,准备与master通信
2.2. 获取主服务器信息
2.1. 主服务器本身的信息: run_id: xxx, role:master
2.2 slave信息 ip, port, state, offset ...
获取到服务器信息之后,即可更新服务器实例信息,如下图:
2.3. 获取从服务器信息
当有新的从服务器通过SLAVEOF成为主服务器的slave时,master的回复信息终将会包含该服务器的信息。Sentinel一方面要为该服务器创建实例结构, 另一方面还需要创建到该服务器的命令连接和订阅连接。连接创建后,sentinel每10秒向从服务器发送INFO命令,获取从服务器的信息,包括run_id, role, masterhost: master_port, 复制偏移量...
2.4. 以一定频率向主、从服务器发送信息
每2秒向所有被监视的主、从服务器发送 PUBLISH sentinel: hello "<...>"命令。
2.5. 接收主、从服务器的信息
通过订阅连接,向服务器发送 SUBSCRIBE sentinel:hello命令。一台sentinel像某台master/slave发送的信息也会被其他订阅了该服务器的sentinel收到。该功能的作用在于:使各个sentinel之间能通过主/从服务器相互通信。 根据收到的消息,如果是自己发送的,那么就丢弃;如果是其他sentinel发送的,就根据信息中的各个参数更新主服务器的实例结构,更新sentinels字典,此时各个sentinel中就可以保存彼此的ip:port,接下来创建链接并相互通信就顺理成章了。
2.6. 检测下线状态
2.6.1 主观下线状态
sentinel默认每秒向所有与他创建了连接的实例(master, slave, sentinel)发送PING命令,通过回复状态来判断实例是否在线。每个sentinel对服务器实例的下线判断都是依靠它自身与服务器之间的连接,有可能二者之间通信不顺畅,导致该sentinel判断对方下线,但并不意味着对方真的下线了,因此这种情况称之为主观下线;实例是否真的下线了,要依靠所有sentinel投票。
回复内容只有3种有效: -PONG, -LOADING, -MASTERDOWN
如果一个实例在一定时间内,连续向sentinel返回无效回复,则sentinel修改该实例对应的状态,将其标记为主观下线。
2.6.2 客观下线状态
当一个sentinel将主服务器标记为主观下线状态之后,会向其他监视该服务器的sentinel发送请求,看他们是否也认为该服务器处于下线状态。当认为下线的sentinel超过配置的阈值时,表明客观下线,将主服务器实例标记为客观下线状态
2.7. 选举sentinel leader
当sentinel将一个主服务器被判定为客观下线之后,会发送请求给监视它的各sentinel,选举出一个leader sentinel,然后由leader sentinel来进行故障转移。
Sentinel集群正常运行的时候每个节点epoch相同,当需要故障转移的时候会在集群中选出Leader执行故障转移操作。Sentinel采用了Raft协议实现了Sentinel间选举Leader的算法,不过也不完全跟论文描述的步骤一致。Sentinel集群运行过程中故障转移完成,所有Sentinel又会恢复平等。Leader仅仅是故障转移操作出现的角色。
选举流程
- 1、某个Sentinel认定master客观下线的节点后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票给其他Sentinel了,在2倍故障转移的超时时间自己就不会成为Leader。相当于它是一个Follower。
- 2、如果该Sentinel还没投过票,那么它就成为Candidate。
- 3、和Raft协议描述的一样,成为Candidate,Sentinel需要完成几件事情
- 1)更新故障转移状态为start
- 2)当前epoch加1,相当于进入一个新term,在Sentinel中epoch就是Raft协议中的term。
- 3)更新自己的超时时间为当前时间随机加上一段时间,随机时间为1s内的随机毫秒数。
- 4)向其他节点发送
is-master-down-by-addr
命令请求投票。命令会带上自己的epoch。 - 5)给自己投一票,在Sentinel中,投票的方式是把自己master结构体里的leader和leader_epoch改成投给的Sentinel和它的epoch。
- 4、其他Sentinel会收到Candidate的
is-master-down-by-addr
命令。如果Sentinel当前epoch和Candidate传给他的epoch一样,他在本轮把票投给了其他Candidate。投过票给别的Sentinel后,在当前epoch内自己就只能成为Follower。 - 5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum(quorum可以参考《redis sentinel设计与实现》)。Sentinel比Raft协议增加了quorum,这样一个Sentinel能否当选Leader还取决于它配置的quorum。
- 6、如果在一个选举时间内,Candidate没有获得超过一半且超过它配置的quorum的票数,自己的这次选举就失败了。
- 7、如果在一个epoch内,没有一个Candidate获得更多的票数。那么等待超过2倍故障转移的超时时间后,Candidate增加epoch重新投票。
- 8、如果某个Candidate获得超过一半且超过它配置的quorum的票数,那么它就成为了Leader。
- 9、与Raft协议不同,Leader并不会把自己成为Leader的消息发给其他Sentinel。其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程。
ref:Raft协议实战之Redis Sentinel的选举Leader源码解析
2.8. 故障转移
8.1. 由leader sentinel重新选取新的master
8.2 修改从服务器的复制目标
8.3 监控旧的master,上线后将其设置为新的master下的slave