sdown
与odown
Redis Sentinel
有两个不同概念的下线:一个被称为主观下线sdown
,一个被称为客观下线odown
。
Sentinel
和其他的Sentinel
保持连接是为了互相之间检查是否可达和交换消息。然而,我们不需要在每个运行的Sentinel
实例中配置其他的Sentinel
地址列表,Sentinel
使用Redis
实例的发布与订阅功能来发现部署中用于监控相同主节点和从节点的其他Sentinel
:
即使没有故障转移,Sentinel
也将尝试把当前的配置设置到监控的实例上。特别注意如下两点:
Sentinel
重新配置从节点,错误的配置在一段时间内会被观察到,这比广播新的配置方式更好,这样可以阻止过时的配置,比如在一个分区中重新加入的Sentinel
在收到更新之前会去交换从节点的配置。需要注意如下事项:
当一个Sentinel
实例准备执行故障转移时,主节点这时在odown
状态下,Sentinel
会收到从大多数已知的其他Sentinel
实例中授权开始故障转移的消息,于是一个合适的从节点会被选举出来。
从节点选举过程评估下列信息:
一个从节点被发现从主节点断开超过主节点配置时间down-after-milliseconds选项
10倍以上,加上从正在执行故障转移的Sentinel
的角度来看主节点不可用的时间,该从节点将被认为是不合适被选举的。在更为严格的条件下,一个从节点从主节点断开超过以下时长将被认为是不可靠的:
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
选举只会考虑通过了上述测试的从节点,并根据上面的条件进行排序,排序说明如下:
如果按优先级选,那么Redis
主节点、从节点都必须配置相应的slave-priority
;否则所有的实例都要有一个默认的ID
。为了永远不被Sentinel
选择为新的主节点,Redis
实例可以把slave-priority
配置为0
,一个这样配置的从节点会被Sentinel
重新配置,唯一不同的是它永远不会成为主节点。
每个被Sentinel
监控的主节点与一个配置的quorum
相关联,它指定了同意主节点是不可达的或者是错误的所需的Sentinel
实例的数量。
在故障转移触发后,为了真正地执行故障转移,大多数的Sentinel
必须授权一个Sentinel
开始执行故障转移。当只有小部分Sentinel
在一个网络分区中,那么故障转移永远不会执行。
如果有5
个Sentinel
实例,quorum
被设置为2
,一旦两个Sentinel
认为主节点不可达,故障转移就会被触发。然而这两个Sentinel
中的一个得到了其他3
个Sentinel
的授权才会开始执行故障转移。把quorum
设置为5,就必须所有这5
个Sentinel
实例同意主节点失败。为了开始故障转移,需要得到所有Sentinel
实例的授权。
这意味着quorum
在两方面可以用来调整Sentinel
:
为了启动故障转移,需要从大多数Sentinel
中得到授权,重要原因如下:
一旦一个Sentinel
能成功地对一个主节点执行故障转移,它就将开始广播新的配置,以便其他Sentinel
更新它们关于主节点的信息。
为了认定一次故障转移是成功的,需要Sentinel
能发送SLAVEOF NO ONE
命令给被选举出来的从节点,然后切换为主节点,稍后就能在主节点的INFO
输出中观察到。
这时,从节点的重新配置正在进行,故障转移也被认为是成功的,并且所有的Sentinel
需要开始报告新的配置。
为新配置采用广播方式的原因是,在每次Sentinel
被授权故障转移时有一个不同的版本号。每个Sentinel
使用Redis
发布与订阅消息来连续不断地广播它的主节点配置的版本号、所有的从节点和主节点。同时,所有的Sentinel
等待消息来查看其他的Sentinel
广播的配置。
配置在__sentinel__:hello
发布与订阅频道中被广播。因为每个配置都有一个不同的版本号,大的版本号总是赢得小的版本号。比如一开始所有的Sentinel
认为主节点mymaster
的配置为192.168.118.128:6379
,这个配置的版本号为1。一段时间后,被授权启动故障转移有了版本号2
,如果故障转移成功,它将广播新的配置(192.168.118.128:6380
,版本号为2
)。所有其他的实例将看到这个配置并更新它们的配置,因为新的配置有更高的版本号。
这意味着Sentinel保证第二个活性属性:一个Sentinel
集合能互相交流并且把配置信息收敛到一个更高的版本号。
基本上,如果网络是分区的,那么每个分区将收敛到一个更高的本地配置。在没有网络分区的特殊情况下,只有一个分区,那么每个Sentinel
将同意这个本地配置。
Redis Sentinel
配置最终是一致的,所以每个分区将收敛到更高的可用配置。在使用Sentinel
的真实世界系统中,有三个不同的角色:
为了定义系统的行为,这三种角色我们都考虑。有一个简单的三个节点网络,每个节点中都运行一个Redis
实例和一个Sentinel
实例,整体架构图如图所示:
在这个系统中,原始状态是Redis-3
是主节点,Redis-1
和Redis-2
是从节点。一个网络分区隔离了旧的主节点。Sentinel-1
和Sentinel-2
启动故障转移过程,把Sentinel-1
提升为新的主节点。
Sentinel
的属性保证Sentinel-1
和Sentinel-2
有了一个主节点的新配置。但Sentinel-3
依然是旧的配置,因为它在一个不同的网络分区中存活。
Sentinel-3
将会更新它的配置,当网络分区治愈时,如果有客户端和旧的主节点在一起,客户端仍然可以向Redis-3
写入数据。当网络分区治愈时,Redis-3
变成Redis-1
的一个从节点,在网络分区治愈期间写入的数据都会丢失。可以通过修改配置选择是否让这种情况发生:
Redis
是采用异步复制的,这种情况下没有办法完全阻止数据的丢失,但是可以使用下面的Redis
配置选项来限制Redis-3
和Redis-1
之间的不一致性:
min-slaves-to-write 1
min-slaves-max-lag 10
上面这两个配置可以减少异步复制和脑裂split-brain
导致的数据丢失,要求至少有1
个从节点,数据复制和同步的延迟不能超过10
秒。一旦所有从节点的数据复制和同步的延迟都超过了10
秒,那么主节点就不会再接收任何命令请求了。
有了min-slaves-max-lag
这个配置选项,就可确保一旦从节点复制数据和确认ack
延时太长,进而认为主节点宕机后损失的数据太多了,拒绝写请求,这样可以把主节点宕机时由于部分数据未同步到从节点而导致的数据丢失降低到可控的范围内。
假如一个主节点出现了脑裂,与其他从节点的连接丢失了,上面的两个配置含义是如果不能继续给指定数量的从节点发送数据,而且从节点超过10
秒没有给主节点发送确认消息,就直接拒绝客户端的写请求。这样脑裂后旧的主节点就不会接收客户端的新数据,也就避免了数据的丢失。因此,在脑裂场景下,最多丢失10
秒的数据。
总之,Redis+Sentinel
是一个最终一致性系统eventually consistent system
,即最后一次的故障转移成功last failover wins
。旧节点中的数据会被丢弃,从当前主节点复制数据,所以总有一个丢失确认写的窗口。这是由Redis
的异步复制和系统的“虚拟”合并功能的丢弃性质决定的。