文章目录
- 初始化 Sentinel
- 三个定时任务(重要)
- INFO任务
- 订阅/发布任务
- 心跳任务
- Redis节点下线判断
- 主观下线判断
- 客观下线判断
- Sentinel Leader 选举
- 故障转移过程
- 整体过程
- Master 选择算法
- 修改从服务器的复制目标
- 将旧的主服务器变为从服务器
- 节点上线
- 原Redis节点上线
- 新Redis节点上线
- Sentinel节点上线
初始化 Sentinel
初始化 Sentinel 的最后一步是创建连向 master 的网络连接,Sentinel将成为 master 的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。
对于每个被 Sentinel 监视的主服务器来说,Sentinel 会创建两个连向主服务器的异步网络连接:
- 命令连接:这个连接专门用于向主服务器发送命令,并接收命令回复。
- 订阅连接:这个连接专门用于订阅主服务器的 __sentinel__ :hello 频道。
为什么要建立两个连接:
在Redis目前的发布与订阅功能中,被发送的信息都不会保存在Redis服务器里面,如果在信息发送时,想要接收信息的客户端不在线或者断线,那么这个客户端就会丢失这条信息。因此,为了不丢失__sentinel__:hello频道的任何信息,Sentinel必须专门用一个订阅连接来接收该频道的信息。
另一方面,除了订阅频道之外,Sentinel还必须向主服务器发送命令,以此来与主服务器进行通信,所以Sentinel还必须向主服务器创建命令连接。
因为Sentinel需要与多个实例创建多个网络连接,所以Sentinel使用的是异步连接。
三个定时任务(重要)
Sentinel 维护着三个定时任务以监测 Redis 节点及其它 Sentinel 节点的状态。
INFO任务
在 Sentinel 的配置文件中有 master 的IP和端口号,所以 Sentinel 在初始时是知道 master 节点的位置,于是 Sentinel 可以向 master 建立命令连接和订阅连接。
Sentinel默认会以每十秒一次的频率,通过命令连接向 master 发送 INFO 命令,并通过分析 master 返回的 INFO 命令回复更新主从节点的相关信息。下面是INFO命令的回复的例子:
# Server
...
run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c
...
# Replication
role:master
...
slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0
...
# Other sections
...
Sentinel 可以获取以下两方面的信息:
- 一方面是关于 master 本身的信息,包括master的运行ID(run_id:7611c59······a9c),以及master的角色(role:master)。
- 另一方面是关于master属下所有slave的信息,每个slave都由一个"slave"字符串开头的行记录,每行的ip域记录了slave的IP地址,而port域则记录了slave的端口号。根据这些IP地址和端口号,Sentinel无须用户提供slave的地址信息,就可以自动发现slave。
当 Sentinel 发现slave的IP和端口号之后就可以向slave发起命令连接和订阅连接,从而获得slave更加具体的信息。Sentinel向slave发送INFO命令,会得到类似如下的回复:
# Server
...
run_id:32be0699dd27b410f7c90dada3a6fab17f97899f
...
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
slave_repl_offset:11887
slave_priority:100
# Other sections
...
根据INFO命令的回复,Sentinel会提取出以下信息:
- slave的运行ID(run_id)。
- slave的角色role。
- master的IP地址master_host,以及master的端口号master_port。
- master的连接状态master_link_status。
- slave的优先级slave_priority。
- slave的复制偏移量slave_repl_offset。
根据这些信息,Sentinel会对slave的相关信息进行更新。
总结:
Sentinel 每隔10s就会通过命令连接向所有的master和slave节点发送INFO命令,根据命令的回复,sentinel可以知道master和slave的各种详细信息,并更新相关数据。
订阅/发布任务
当Sentinel与一个master或者slave建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:
SUBSCRIBE __sentinel__:hello
Sentinel对 __sentinel__:hello 频道的订阅会一直持续到Sentinel与Redis节点的连接断开为止。
订阅成功后,每个 Sentinel 节点每 2 秒就会向每个 Redis 节点发布一条__sentinel__ :hello 主题的信息(通过命令连接发送publish命令),当 Redis 节点中该主题的信息发生了变化,就会立即通知到所有订阅者(Sentinel)。举个例子,假设现在有sentinel1、sentinel2、sentinel3三个Sentinel在监视同一个master,那么当sentinel1向master的 __sentinel__ :hello 频道发送一条信息时,所有订阅了 __sentinel__ :hello 频道的Sentinel(包括sentinel1自己在内)都会收到master的返回信息。
当一个Sentinel从 __sentinel__:hello 频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址、Sentinel端口号、Sentinel运行ID等八个参数(通过这些信息,当前的Sentinel就可以知道其他Sentinel的IP和端口号,从而向其他Sentinel建立连接),并进行以下检查:
- 如果本地信息中记录的Sentinel运行ID和接收信息的Sentinel的运行ID相同,那么说明这条信息是Sentinel自己发送的,Sentinel将丢弃这条信息,不做进一步处理。
- 如果本地信息中记录的Sentinel运行ID和接收信息的Sentinel的运行ID不相同,那么说明这条信息是监视同一个Redis节点的其他Sentinel发来的,接收信息的Sentinel将根据信息中的各个参数,对相应master节点的相关信息进行更新(Sentinel节点本地存储了master节点的相关信息,对这里面的信息进行更新)。
- 如果接收信息的Sentinel的运行ID在本地中不存在,那么说明有新建立的Sentinel节点对master节点的监控,当前Sentinel节点会在master的记录中增加一条新Sentinel节点的信息。
当Sentinel通过频道信息发现一个新的Sentinel时,它不仅会为新Sentinel创建相关记录信息,还会创建一个连向新Sentinel的命令连接,而新Sentinel也同样会创建连向这个Sentinel的命令连接,最终监视同一master的多个Sentinel将形成相互连接的网络。
Sentinel之间不会创建订阅连接
Sentinel在连接master或者slave时,会同时创建命令连接和订阅连接,但是在连接其他Sentinel时,却只会创建命令连接,而不创建订阅连接。这是因为Sentinel需要通过接收master或者slave发来的频道信息来发现未知的新Sentinel,所以才需要建立订阅连接,而相互已知的Sentinel只要使用命令连接来进行通信就足够了。
总结:
每个 Sentinel 节点每 2 秒就会向每个 Redis 节点发布一条消息。因为一个Sentinel可以通过分析接收到的频道信息来获知其他Sentinel的存在,并通过发送频道信息来让其他Sentinel知道自己的存在,所以用户在使用Sentinel的时候并不需要提供各个Sentinel的地址信息,监视同一个master的多个Sentinel可以自动发现对方。
心跳任务
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的节点(包括master、slave、其他Sentinel在内)发送PING命令,并通过节点返回的PING命令回复来判断节点是否在线。
节点对PING命令的回复可以分为以下两种情况:
- 有效回复:实例返回+PONG、-LOADING、-MASTERDOWN三种回复的其中一种。
- 无效回复:实例返回除+PONG、-LOADING、-MASTERDOWN三种回复之外的其他回复,或者在指定时限内没有返回任何回复。
Redis节点下线判断
主观下线判断
Sentinel配置文件中的down-after-milliseconds选项指定了Sentinel判断Redis节点进入主观下线所需的时间长度:如果一个节点在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个节点的相关消息,把该节点的flags属性增加SRI_S_DOWN标识,以此来表示这个实例已经进入主观下线状态。
用户设置的down-after-milliseconds选项的值,不仅会被Sentinel用来判断master的主观下线状态,还会被用于判断master属下的所有slave,以及所有同样监视这个master的其他Sentinel的主观下线状态。举个例子,如果用户向Sentinel设置了以下配置:
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel down-after-milliseconds mymaster 30000
那么30000毫秒会成为Sentinel判断master、master属下所有slave、监视该master的其他Sentinel进入主观下线的标准。
注意:
在Redis的分布式集群中,多个Sentinel设置的主观下线时长可能不同。这样可能会造成Sentinel1认为master主观下线,而Sentinel2并不认为master主观下线的情况。
客观下线判断
当Sentinel将一个master判断为主观下线之后,为了确认这个master是否真的下线了,它会向同样监视这一master的其他Sentinel进行询问,看它们是否也认为master已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量
(数量大于等于 quorum )的已下线判断之后,Sentinel会将master的flags属性增加SRI_O_DOWN标识,表示master已经进入客观下线状态,并对master执行故障转移操作。
Sentinel Leader 选举
当 Sentinel 节点对 master 做出客观下线判断后会由 Sentinel Leader 来完成后续的故障转移,即 Sentinel 集群中的节点也并非是对等节点,是存在 Leader 与 Follower 的。
Sentinel 集群的 Leader 选举是通过 Raft 算法实现的。Raft 算法比较复杂,详见:Raft算法详解 - 知乎 。这里仅简单介绍一下大致思路。
每个选举参与者都具有当选 Leader 的资格,当其完成了“客观下线”判断后,就会立即“毛遂自荐”推选自己做 Leader,然后将自己的提案发送给所有参与者。其它参与者在收到提案后,只要自己手中的选票没有投出去,其就会立即通过该提案并将同意结果反馈给提案者,后续再过来的提案会由于该参与者没有了选票而被拒绝。当提案者收到了同意反馈数量大于等于 max(quorum,sentinelNum/2+1)时,该提案者当选 Leader。
说明:
在网络没有问题的前提下,基本就是谁先做出了“客观下线”判断,谁就会首先发起Sentinel Leader 的选举,谁就会得到大多数参与者的支持,谁就会当选 Leader。
Sentinel Leader 选举会在次故障转移发生之前进行。
故障转移结束后 Sentinel 不再维护这种 Leader-Follower 关系,即 Leader 不再存在。
故障转移过程
整体过程
在选举产生出Sentinel Leader之后,Sentinel Leader 将对已下线的master执行故障转移操作,该操作包含以下三个步骤:
- 在已下线master属下的所有slave里面,挑选出一个slave,并将其角色转换为master。
- 让已下线master属下的所有slave改为复制新的master。
- 将已下线的master设置为新master的slave,当这个旧的master重新上线(重启)时,它就会成为新master节点的slave。
故障转移操作第一步要做的就是在已下线的master属下的所有slave中,挑选出一个状态良好、数据完整的slave,然后向这个slave发送SLAVEOF no one命令,将这个slave转换为master。
Master 选择算法
在进行故障转移时,Sentinel Leader 需要从所有 Redis 的 Slave 节点中选择出新的 Master。其选择算法为:
- 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
- 删除列表中所有最近五秒内没有回复过Sentinel Leader的INFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
- 删除所有与已下线master连接断开超过down-after-milliseconds*10毫秒的slave:down-after-milliseconds选项指定了判断主服务器下线所需的时间,而删除断开时长超过down-after-milliseconds*10 毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。
- 删除replica-priority(优先级)值为 0 的 Redis节点。
- 在剩余 Redis 节点中选择出 replica-priority 最小的的节点列表。如果只有一个节点,则直接返回,否则,继续。
- 从优先级相同的节点列表中选择复制偏移量最大的节点(和已宕机的master节点的相似度最高)。如果只有一个节点,则直接返回,否则,继续。
- 从复制偏移值量相同的节点列表中选择动态 ID 最小的节点返回。
对最后选择的slave发送SLAVEOF no one命令之后,Sentinel Leader会以每秒一次的频率(平时是每十秒一次),向被升级的从服务器发送INFO命令,并观察命令回复中的角色(role)信息,当被升级服务器的role从原来的slave变为master时,Sentinel Leader就知道被选中的从服务器已经顺利升级为master了。
修改从服务器的复制目标
当新的主服务器出现之后,Sentinel Leader下一步要做的就是,让已下线master属下的所有slave去复制新的master,这一动作可以通过向slave发送SLAVEOF命令来实现。
将旧的主服务器变为从服务器
故障转移操作最后要做的是,将已下线的master设置为新的master的slave。因为旧的master已经下线,所以这种设置是保存在旧master的记录信息中,当旧master重新上线时,Sentinel (不是Sentinel Leader)就会向它发送SLAVEOF命令,让它成为新master的slave。
节点上线
原Redis节点上线
无论是原下线的 master 节点还是原下线的 slave 节点,只要是原 Redis 集群中的节点上线,只需启动 Redis 即可。因为每个 Sentinel 中都保存有原来其监控的所有 Redis 节点列表,Sentinel 会定时查看这些 Redis 节点是否恢复。如果查看到其已经恢复,则会命其从当前master 进行数据同步。不过,如果是原 master 上线,在新 master 晋升后 Sentinel Leader 会立即先将原 master 节点更新为 slave,并同步新master节点的数据。
新Redis节点上线
如果需要在 Redis 集群中添加一个新的节点,其未曾出现在 Redis 集群中,则上线操作只能手工完成。即添加者在添加之前必须知道当前 master 是谁,然后在新节点启动后运行slaveof 命令加入集群。
Sentinel节点上线
如果要添加的是 Sentinel 节点,无论其是否曾经出现在 Sentinel 集群中,都需要手工完成。即添加者在添加之前必须知道当前 master 是谁,然后在配置文件中修改 sentinel monitor属性,指定要监控的 master。然后启动 Sentinel 即可。