初始化 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...# Replicationrole:master...slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0slave2: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...# Replicationrole:slavemaster_host:127.0.0.1master_port:6379master_link_status:upslave_repl_offset:11887slave_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__:helloSentinel对 __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 2sentinel 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 即可。