NoSQL之Redis---哨兵(sentinel)

时间:2020-12-23 08:48:10

[不忘初心]

在搭建分布式集群中,为了提供高可用性,Redis还提供了哨兵(sentinel)机制。本文,我们就来看看这部分的内容。本文的翻译内容鉴于博主英语太渣,各位看官如果发现不合理的地方还请及时指出。官方文档:http://redis.io/topics/sentinel

-------------------------------------------------------------------------------------------------------------------------------------

Redis Sentinel 为Redis集群提供了高可用性,在实际的项目中意味着你可以使用Sentinel创建一个部署环境,其能够在不用人工干预的处理某些错误情况。

Redis Sentinel 同样提供其他额外的任务功能,如,监控,通知,和作为客户端配置的提供者。下面是Redis Sentinel从宏观上所拥有的能力。

  • 监控(Monitoring):Sentinel会持续不断的检查你的master节点和slave节点是否按照预期的那样正常工作。
  • 通知(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向系统管理员或者其他应用程序发送通知。
  • 自动故障转移(Automatic failover):如果master节点不是按照预期的那样工作,Sentinel能够开始一个故障转移操作,其将一个slave节点提升为 一个master节点, 并让其他slave节点更改对应的master为新的master节点。当客户端尝试连接失效的master服务器时,Sentinel也会通知客户端新的master服务器地址。
  • 配置提供者(Configuration provider):Sentinel 作为一个为客户端服务发现的权威来源:客户端连接Sentinel节点目的是为了询问对一个给定服务的当前master节点的地址。如果发生故障转移,Sentinel也会通知新的地址。

Sentinel的分布式

Redis Sentinel是一个分布式系统:
Sentinel本身被设计为能够在一个框架中,运行多个Sentinel进程。这么做的好处如下:

  1. 当多个Sentinel同意一个事实:一个给定的master节点不再可用时,执行故障检测。这种做法降低了误报的概率。
  2. Sentinel能够在不是所有Sentinel进程都正常的情况下仍然工作,使得系统拥有鲁棒性。毕竟,如果一个故障转移系统本身就是一个单点故障,这样的话是无意义的事情。

Sentinel的总数目,Redis实例(master节点,slave节点)连接至Sentinel与Redis的客户端,都是带有特定属性的分布式系统。在本文的概念中将会逐步介绍:从所需要的基本信息开始,来理解的Sentinel基本属性,然后再介绍稍微难一点的信息(当然,是可选的)来理解Sentnel是如何工作的。

Quick Start

获取Sentinel

当前Sentinel的版本被称为Sentinel 2。这是一个使用更强大更简单的预测算法,重写了初始Sentinel版本的实现。(这个算法将在本文中进行解释)。 其中一个Redis Sentinel的稳定版本包含在2.8与3.0版本中,这时目前最新的稳定版本。在unstable分支中的新增内容,以及新特性,当被视为已经稳定时,会尽快的加入到2.8与3.0版本中。 Redis Sentinel的第一个版本,包含在2.6版本中,已经过时了并且也不再推荐使用了。

启动Sentinel

如果你使用redis-sentinel可执行文件,(或者你有一个链接到redis-server程序的链接),那么可以使用下面的命令来启动Sentinel系统
redis-sentinel /path/to/sentinel.conf
或者可以直接使用redis-server程序,使用sentinel模式启动:
redis-server /path/to/sentinel.conf --sentinel
这两种方法的运行效果都是一样的。
但是,当运行Sentinel时,强制使用一个配置文件,这个文件将会被系统用来保存当前状态,并且在重新启动Sentinel时使用。Sentinel将会直接拒绝在没有提供文件,或者对配置文件的路径没有写权限时,启动服务。 Sentinel默认运行在TCP协议的26379端口上,这个端口专门用来给Sentinel使用,所以,你的服务器上26379端口必须保持开启,使得其能够接受其他Sentinel实例的地址链接。否则,Sentinel就不能够与其他实例进行会话和参与协作,结果就是故障转移将不会正常运行。

在部署Sentinel之前,你需要了解的基本事项:

  1. 你至少需要3个Sentinel示例来搭建一个具有鲁棒性的系统。
  2. 这3个Sentinel示例应该被部署在3个独立的机器或者虚拟机中,使其能够以独立的方式陷入失败。举个例子,在不同物理机服务器或者虚拟机执行在不同的可用区域上。
  3. Sentinel+Redis的分布式系统不能够保证在发生故障期间将接受的写命令全部都持久化下来,因为,Reids使用的是异步复制的方式。但是,在使用缺少安全的方式来部署时,有办法让Redis在特定的部署方式下,只丢失有限时间的写操作
  4. 你需要在你的客户端中添加Sentinel支持。流行的客户端库都拥有对Sentinel支持。但也不是所有的都支持。
  5. 如果你不再部署环境中不断的测试,那就没有高可用的安全的集群。或者,就算在生产环境下这么做了,集群也正常工作。你可能有一个错误的配置,当在这个错误明显的暴露时,可能已经为时过晚。
  6. Sentinel,Docker,或者其他定时的网络地址转换,或者端口映射都应该被关注到:Docker执行端口的映射,打断Sentinel的自动发现其他Sentinel进程和一个master节点的slave节点列表。在本文之后的内容,将介绍更多这部分内容。

配置Sentinel

Sentinel源码中包含一个sentinel.conf的文件,这个文件是一个带有详细注释的Sentinel配置文件示例。下面是运行一个Sentinel所需的最少配置:

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5

你只需要指明一个监控的master对象,对每一个master对象(可能有任何数量的slave)赋予不同的名称。此处,是没有必要指明slave节点的,因为Sentinel有自动发现的功能。Sentinel将会自动更新关于slave节点额外信息的配置(为了在重新启动是仍然保留这些配置信息)。这个配置同样将会在slave提升为master节点,或者是在每次发现一个新的Sentinel时重写。

上面的示例配置,包含两组Redis实例的监控,每一个都包含一个master节点和一组未定义数量的slave节点。其中一组称为mymaster,另一组称为resque。

Sentinel监控语句的参数含义如下:

sentinel monitor <master-group-name> <ip> <port> <quorum>

为了清晰起见,我们来一个一个的介绍参数配置项的含义:

第一行配置:为Sentinel指定所监控的master节点对象,配置方法为NAME(mymaster)+IP(127.0.0.1)+PORT(6379)+最少的Sentinel表决数(N=2) ,即将该NAME的master节点判定为失效,至少需要N个Sentinel同意。(反之,只要表决数量少于N,就不会发生自动的故障转移)。前面的几个参数含义是非常明确的,但这个quorum(法定人数)的含义需要进一步解释:

  • quorum参数是Sentinel所有节点中需要同意一个master节点失去连接的最少数量,这么做是为了标记某个slave失效,并且可能的话,开始一个故障转移过程。
  • 但是,quorum参数仅仅用来发现故障。为了实际执行一个故障转移过程,其中一个Sentinel需要被选举出来领导故障转移和在过程中被授权。这仅仅发生在多数Sentinel节点投票过程。

因此,举个例子,假设你拥有5个Sentinel进程,并且对一个给定的master节点的quorum参数配置为2,接下的发生事情如下:

  • 如果2个Sentinel节点同时同意这个master节点不可访问,其中一个Sentinel将会尝试开始一个故障转移。
  • 如果这里至少3个Sentinel是可访问的,故障转移过程将是被允许的,并且实际的开始运行。

在实际项目中,这意味着在故障发生期间,如果Sentinel节点的多数节点不能够通讯,那么Sentinel绝不会开始故障转移过程。

其他Sentinel功能的参数选项:

其他参数的配置格式如下:

sentinel <option_name> <master_name> <option_value>
各个选项的功能如下:

  • down-after-millseconds:指定Sentinel任务服务器已经断线的毫秒数。(断线是指:没有应答PING命令,或者返回一个错误)。
  • parallel-syncs:指定了在故障转移期间,最多可以有多少个slave服务器同时对新的master服务器进行同步,这个数字越小,完成故障转移所需的时间越长。但是,如果slave被配置为允许使用过期的数据集,那么你可能不希望所有的slave服务器都在同一时间向新的master服务器发送同步请求。因为尽管复制过程的绝大部分步骤都不会阻塞slave服务器,但slave服务器在载入master服务器的RDB文件时,仍然会造成slave服务器在一段时间内不能处理命令。如果全部的slave服务器同时对新的master服务器进行同步,那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。那么,你可以通过将这个值设置为1,来保证每次只有一个slave服务器处于不能处理命令请求的状态。

本文档剩余的内容将对 Sentinel 系统的其他选项进行介绍, 示例配置文件 sentinel.conf 也对相关的选项进行了完整的注释。

所有的参数选项可以在运行期间使用SENTINEL SET命令进行修改。在下文中我们会详细介绍。

Sentinel部署示例

现在,你已经了解了关于Sentinel的基本信息,你可能想要知道在哪里部署自己的Sentinel进程,多少数量合适等等。本节内容展示了一些部署示例。我们使用了字符画(ASCII art)用图形的格式来为你展示一个配置示例,下面就是不同的符号含义:

NoSQL之Redis---哨兵(sentinel)

我们在box中写出了具体的运行信息:
NoSQL之Redis---哨兵(sentinel)

两个不同的box用直线连接表示它们之间能够进行会话:

NoSQL之Redis---哨兵(sentinel)

发生网络分片时,使用反斜线表示:

NoSQL之Redis---哨兵(sentinel)

同时,请注意下面的名称配置说明:

  • Master节点简写为:M1,M2,M3,,,,Mn。
  • Slave节点简写为:R1,R2,R3,,,,Rn(R代表副本(replica)
  • Sentinel节点简写为:S1,S2,S3,,,,Sn
  • 客户端节点简写为:C1,C2,C3,,,,Cn
  • 当节点因为Sentinel的功能行为发生角色转变时,我们用中括号将其包围起来,因此,【M1】表示因为Sentinel中断作用产生的新节点。
特别注意:不要使用只有2个Sentinel的场景,因为Sentinel总是需要与多数节点通信来开始一个故障转移过程。

Example 1:只有2个Sentinel,千万别这么做!

NoSQL之Redis---哨兵(sentinel)
  • 在这种配置之下,如果M1节点失效,R1将会被提升为master,因为2个Sentinel对故障能够达成一致意见(因为quorum=1),并且也能够授权一个故障转移,因为多数节点数量为2。因此,显然发生的情况是,表面上其能够正常的进行工作,但是确认下面第二点来看看为什么这种配置是错误的。
  • 如果M1停止工作的box中的S1也停止工作。S2节点将不能够授权进行故障转移,于是整个系统将会彻底不可用。
特别注意:此时需要大多数节点同意(虽然quorum=1,但多数节点是指2)才能开始故障转移,以及之后将新的配置文件传递给所有的Sentinel节点。同时还需要注意的是:在孤立的一侧配置其不用协商就拥有故障转移的能力也是十分危险的行为。如下:
NoSQL之Redis---哨兵(sentinel)
在上面的配置中,我们以完全对称的方式创建了2个master节点(假设S2能够在没有授权时进行故障转移)。客户端节点可能无限制的同时写两边,并且也无法知道是那么时刻分片结束与结束之后的正确配置在哪一边。为了防止出现永久的分裂状态,因此,请至少在3个box中部署3个Sentinel节点。 

Example 2:用3个box搭建最低配置的集群

这是一个非常简单的配置,其优点是:很容易的转为具有额外的安全能力。这种结构如下:3个box,每一个box同时运行着Redis节点,Sentinel节点。 NoSQL之Redis---哨兵(sentinel)
如果M1节点失效,S2,S3将会同意这个事实并且能够授权开始一个故障转移,使得客户端能够继续使用集群服务。 在每一个Sentinel配置时,将会使用Redis的异步复制,这里将会出现一个风险:因为异步复制,导致一个给定的,已经接受的写命令,可能还没有被复制到slave中时,这个slave已经被提升为master节点,从而导致丢失了该命令。然而,在上面的配置模型中,还拥有一个更高的风险:C1与旧的master被分隔到另一个网络环境中,就像下面的结构: NoSQL之Redis---哨兵(sentinel)

在这种情况下,网络分片将旧的master节点M1隔离开来,因此,Slave节点R2将被提升为master节点。但是,客户端,如C1,在与旧的master节点相同的分片网络中,可能会继续向旧的master写数据。但是,当分片网络重新合并时,这些写入的数据将会永久性的丢失,因为,此时旧的master节点会成为新的master节点的slave节点,并且丢弃它原有的数据集。

这个问题可以使用下面的复制配置项来适当规避,那就是master节点如果不能够与给定数量的slave节点进行通信的话,就停止接受写命令。配置如下:

min-slaves-to-write 1
min-slaves-max-lag 10
如果一个Redis的master节点使用上面的配置,如果其至少没有1个slave节点,其将不再接受写命令。因为是异步复制的策略,不接受写命令实际上意味着slave也断开链接了,或者不能回复我们异步接受的指定max-lag的最大超时秒数。

在上面的例子中,M1使用这样的配置,那么将会在10s之后不可用。当分片网络重新连接后,Sentinel配置将会转向新的节点,客户端,C1节点将会获得新的有效配置,并且成为新的master节点。

但是,这种模式也不是绝对安全的。在这种细化的模式下,如果2个slave节点都失效了,master节点将停止接受写命令,整个集群服务都会失效。(原因是:min-slaves-to-write =1,即至少需要一个slave节点)

Example 3:在客户端Box中加入Sentinel

有时,我们只有两个可用的box,一个归master,一个归slave。这种配置在上面Example 2中是不可行的,但是,我们可以采取下面的策略,将Sentinel与Client部署在一起,如下:

NoSQL之Redis---哨兵(sentinel)

在这种配置下,Sentinel的视角和client是一样的:如果一个master能够被多数client链接,那就是正常的情况。C1,C2,C3,在此是一般意义的客户端,并不是说C1表示一个单独链接到Redis的客户端。其更像是一个应用服务器,一个Redis的app,或者类似的东西。

如果M1与S1所在的box失效了,将会毫无疑问的发生故障转移,但是,很容易发现的是不同的网络分片导致不同的结果。举个例子:如果client与server之间的网络断线,Sentinel将不能够正常运行,因为Redis的master节点与slave节点同时变得不可用。

注意,如果C3与M1处于同一个分片中,(几乎不是由于网络分片导致的,更多是不同的节点布局,或者软件成的故障导致),那么我们就发现了与example 2相似的问题,所不同的是这里我们无法打破对称,因为只有一个slave与master,因此,master节点在于其slave节点失去连接时,不能停止接受操作,否则,master节点在slave发生故障期间将进入永久不可用的状态。

因此,相比于Example 2 这是一个有效的配置,其优点如:Redis的高可用系统运行在相同box中,Redis本身变得更加容易管理,并且有能力将绑定链接的master节点放入包含少数节点的分片网络中。

Example 4:Sentinel处于少于3个client的一侧。

在Example 3的描述的配置如果在client一侧,没有足够的3个box(如3个web server),是不可用的。在这种情况下,我们需要采取下面的混合配置,如下:

NoSQL之Redis---哨兵(sentinel)

这和上面Example 3的配置类似,但是这里我们可用的4个box中运行了4个Sentinel。如果M1失效,其他3个Sentinel将会执行故障转移。

在这个配置方式下,我们移除C2和S4所处的box,并且设置quorum的值为2.然而,其结果并达不到在Redis Server一侧实现自动切换,并保证应用层的高可用性

Sentinel,Docker,NAT及其他可能的问题

Docker使用了一种称为端口映射的技术:运行在Docker容器内部的进程可能向外暴露了另一个相比于程序实际使用的不同的端口号。这么做的意义在于:同一时间,在一台server上,使用相同的端口来运行多个容器。

Docker不是唯一使用这种方案的技术,其他的Network Address Translation 也可能配置了端口重映射,并且某些情况下并不适用端口而是使用IP地址。

重新映射端口和使用Sentinel进行地址创建的问题使用如下两种方式:

  1. Sentinel自动发现其他的Sentinel停止工作,其功能建立在Sentinel主动在连接上监听端口或者IP中受到其他每一个Sentinel发出的hello消息。但是,Sentinel无法了解到地址或者端口的重新映射,因此,相对于内部的端口,Sentinel可能声明了一个错误的信息给其他客户端来链接。
  2.  类似的方法,对Redis的master节点使用INFO命令列出其slave节点信息:地址信息是有master节点检查TCP远程连接的节点监测到的,但是端口却是slave节点在握手协议中自己声明的,因此,这个端口的值可能向上面例子的原因一样发生错误。

由于Sentinel使用master节点上INFO命令的输出信息来发现slave节点,已经发现的slave节点不可达,Sentinel将没有能力执行故障转移,因为从系统的角度,没有正常工作的slave节点,因此,现在还没有方法使用Sentinel监控一组用Docker部署的master节点和slave节点,除非你指定Docker用一对一的方式映射端口。

对于第一个问题,如果你想运行使用重定向端口的Docker部署的一组Sentinel的实例(或者其他具备端口重映射的NAT配置),你可以使用下面的两条Sentinel配置,目的是强制Sentinel声明一个指定的IP和端口。

sentinel announce-ip <ip>
sentinel announce-port <port>
Note that Docker has the ability to run in host networking mode (check the --net=host option for more information). This should create no issues since ports are not remapped in this setup。(还没用过Docker,各位看官先自己学习下吧)。

快速指南

在本文接下来的部分,我们将逐步介绍所有Sentinel API的细节内容,配置项,及其中详细的关键点。但对于那些想尽快运行起来整个系统模型的用户,本节将会展示如何配置和与3个Sentinel实例进行交互的一个基本教程。

这里我们假设实例运行的端口为5000,5001,5002。同时假设我们有一个运行在6379端口上的master节点,对应的slave运行在6380端口上。我们将在整个教程中使用IPV4的本地环路地址127.0.0.1作为IP,假设你是在个人电脑上进行运行一个仿真环境。

我们以由3个Sentinel组成的网络结构为例,给出基本配置内容,如下:

port 5000
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
其他两份配置文件只需要将端口分别改为5001,5002即可。

关于配置文件中的注意事项如下:

  • master节点的名称为mymaster。其指明了一个master节点和对应的slave节点。由于每一个master节点的名称都不相同,因此Sentinel可以同时监控不同的master节点以及slave节点。
  • 参数quorum的值配置为2。
  • 参数down-after-millseconds的值配置为5000毫秒,意思是如果在5000毫秒之内,master节点如果没有返回ping命令,其就会判定为失效。

一旦你启动了3个Sentinel,将会看到如下的日志输出:

+monitor master mymaster 127.0.0.1 6379 quorum 2
这是一个Sentinel事件,并且如果你SUBCRIBE这个指定事件名称之后,就可以通过PUB/SUB命令接收到这种类型的事件。

Sentinel将在发现失效和故障转移过程中将产生不同的事件并且输出到日志。

向Sentinel询问关于master节点的运行状态

启动Sentinel时最显然应该做的事情就是判断master节点是否正常工作:
$ redis-cli -p 5000
127.0.0.1:5000> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "953ae6a589449c13ddefaee3538d356d287f509b"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "735"
19) "last-ping-reply"
20) "735"
21) "down-after-milliseconds"
22) "5000"
23) "info-refresh"
24) "126"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "532439"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "1"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "60000"
39) "parallel-syncs"
40) "1"
正如上面所看到的,该命令打印出了所有关于master节点的信息。这里有一些值得非常值得注意的地方,如下:

  1. 参数num-other-sentinels的值为2,因此我们知道当前的Sentinel已经发现了对当前master节点额外监控的2个Sentinel节点。如果回头检查日志就会发现“+Sentinel”事件在其中。
  2. 参数flags只有master。如果master下线了,我们就能到在这里看到s-down或者o_down。
  3. 参数num-slave的值为1,所以Sentinel发现当前的master节点只有1个slave节点。

更多的信息,可以使用下面的两条命令获取:

SENTINEL slaves mymaster
SENTINEL sentinels mymaster
第一条将会提供关于slave节点的详细信息。

第二条将会提供关于其他Sentinel节点的信息。

获取当点master节点的地址

正如我们已经指明的那样,Sentinel也扮演了一个配置提供者的角色给那些想连接至master节点或者slave节点的client。由于可能发生故障转移,重新配置Server之后,client可能不知道当前集群网络中的哪个节点是当前真正的master节点,因此,Sentinel向外暴露了这样的接口供外部使用:

127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"

测试故障转移

在本节中,我们机器上的Sentinel部署环境需要提前准备好。接下来,结束master进程,并且检查配置是否改变。为了达到这个目的,我们使用如下的命令:

redis-cli -p 6379 DEBUG sleep 30
该命令将会使master节点不再接受访问,并sleep 30秒。基本等于是模拟一个master节点因为某些原因发生一段时间内的宕机效果。

如果你检查Sentinel的日志,就会发现下面的内容:

  1. 每一个监视该master节点的Sentinel节点都会产生一个“+sdown”事件
  2. 这个时间之后会转化为“+odown”,这意味着,多个Sentinel已经同意这个master节点失效。
  3. Sentinel将会选举出一个Sentinel来指挥尝试第一次的故障转移。
  4. 故障转移开始。
此时,我们再次使用上面的命令,询问当前master节点的地址,我们会得到另外一个不同的结果:
127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6380"
到目前为止,整体内容都很简单,在本节,你可以直接创建一个Sentinel部署环境来学习这些内容,或者学习下面更多内容来理解所有关于Sentinel的命令和内部原理。

Sentinel API

Sentinel提供以充足的API来监控其状态,检查master节点,slave节点的运行状况,订阅接受特殊的通知和在运行时改变Sentinel的配置信息。
默认情况下,Sentienl使用26379端口。Sentinel接受使用Redis协议的命令,因此,你可以使用redis-cli或者任何其他未修改的Redis客户端来与Sentinel进行数据交换。当然,也可以直接查询Sentienl,从Sentienl的角度来检查其监控的Redis实例的运行状态,询问记录的其他Sentienl信息等等。除此之外,可选的还有,使用Pub/Sub,其可以接收到来自Sentinel的push类型的通知,如:发生某些事件(故障转移,实例进入错误状态等等)。  Sentinel 命令 下面使一些Sentinel接受的命令,未列出的部门将在下文中再做介绍:
  • PING,返回PONG
  • SENTINEL master:列出所有被监控的master节点,以及对应的状态信息。
  • SENTINEL master <master name>:列出指定的master节点,以及这些节点对应的状态信息。
  • SENTINEL slaves <master name>:列出指定的master节点的所有slave节点,以及这些节点对应的状态信息。
  • SENTINEL sentinels<master name>:列出指定的master节点的Sentinel节点,以及这些节点对应的状态信息。
  • SENTINEL get-master-addr-by-name<master name>:返回指定name的master节点的IP与端口。如果这个master节点正在进行故障转移,或者针对这个master节点的故障转移操作已经完成,那么该命令将会返回新的master节点的IP与端口。
  • SENTINEL reset <pattern>:该命令将会重置所有匹配名称的master节点。pattern这个参数是Glob模式的。重置操作清楚知道当前master节点的状态,(包括正在执行故障转移),并以移除目前已经发现和关联到master节点的所有slave节点和对应的Sentinel。
  • SENTINEL failover<master name>:当master节点失效时,在不询问其他Sentinel的条件下,强制开始一次自动的故障转移操作。(但是,发起故障转移的Sentinel会向其他Sentinel发送一份新版本的配置文件,其他Sentinel会跟据该文件进行相应的更新)。
  • SENTINEL ckquorum<master name>:检查当前Sentinel的配置能否达到故障转移一个master节点所需要的quorum配置,并且多数节点需要授权进行故障转移。这个命令应该在监控系统中检查Sentienl部署是否正确。
  • SENTINEL flushconfig:强制Sentienl将其配置信息写入磁盘,包括当前Sentinel状态信息。通常情况下Sentinel是在其发生变化时才重写配置(状态的子集的上下文持久化到磁盘中,在重启时仍然有效)。但是,该操作有可能会丢失,原因如:错误的操作,磁盘故障,打包升级脚本,或者管理员配置错误等。在这些情况下,为了手动调用此命令来强制将当前信息写入磁盘。即使之前的文件彻底丢失,该命令仍然能够正常运行。

在运行时重新配置Sentinel

从2.8.4版本开始,Sentinel提供了一组API来实现增加,删除,或者改变一个给定的master节点的配置。注意,如果你有多个运行的Sentinel,那你应该让改变的配置信息都作用与其他的Sentinel上,使得Sentinel的功能正常运行。这意味着:在单个节点上改变配置,这些新的配置信息不会自动的传递给其他的Sentinel节点。

下面的命令,可以用来在Sentienl之间更新配置信息:

  • SENTINEL MONITOR <name> <ip> <port> <quorum>:该命令指示Sentinel监控一个给定name, ip,port,quorum信息的master节点。 这和在sentinel.conf文件中配置的内容是一样的,唯一的区别是你不能使用主机名称,而是需要提供一个IPV4或者IPV6的地址。
  • SENTINEL REMOVE <name>:该命令用来移除给定的master节点:该master节点将不再被监控,并且从Sentinel内部状态中移除,因此,该master节点将不再显示在SENTINEL master命令的返回值中。
  • SENTINEL SET <name><option><value>:该命令非常类似于Redis的CONFIG SET命令,被用来改变给定master节点的参数配置信息。给定多组配置信息也够被识别。所有通过sentinel.conf文件配置的参数也可以通过SET命令改变。

下面给出一个SENTINEL SET命令的示例来说明:

SENTINEL SET objects-cache-master down-after-milliseconds 1000
正如刚刚提到的,SENTINEL SET能够被用来配饰所有可以在配置文件中配置的参数。此外,其还可以只改变master节点的quorum配置,而不用增加或者删除master节点,仅仅使用下面的命令即可:
SENTINEL SET objects-cache-master quorum 5
注意:这里没有与GET命令等效的命令,因为SENTINEL MASTER命令已经用非常简单的k-v形式提供所有配置信息。

增加或者移除Sentinel

在部署环境中增加一个Sentinel是非常简单的,因为Sentinel本身已经实现了自动发现的机制。你所需要做的只有为当前活跃的master的节点启动新的Sentinel配置即可。在10s钟之内,新的Sentinel将会感知到其他Sentinel和master节点的slave节点集合。如果你需要增加多个Sentinel,推荐的做法是一个一个的增加,等待上一个Sentinel完全被其他Sentienl接受之后再加入下一个的Sentinel。这种做法能够持续的保证多数节点在分片网络中能够保持连接,防止在添加新的Sentinel过程中发生故障。

这种设计方案可以很容易实现:在没有发生网络分片的情况下,以30秒为间隔,创建每一个Sentinel。

在整个操作过程的最后,可以使用SENTINEL MASTER mastername命令来检查是否所有的Sentinel都已经实现对master节点进行监控。

但是,移除一个Sentinel想对的复杂一点:Sentinel 绝对不会忘记已经连接上的其他Sentinel,即使某个Sentinel在很长一段时间内都是不可达的状态。采取这种策略的原因是:我们不想动态的改变需要授权一个故障转移和创建一个新配置的大多数节点的数量。因此,为了达到这个目的,我们需要采取下面的步骤来实现在没有网络分片的情况下移除一个Sentinel:

  1. 停止你将要移除的Sentinel进程。
  2. 发送SENTINEL RESET * 命令给所有其他Sentinel实例(如果你仅仅想重置一个指定的节点,也可以使用指定的名称来替代*)。一个接一个的,两步操作之间至少等待30秒。
  3. 检查所有Sentinel都同意仍然在活动状态的Sentinel实例,方法为:在每个Sentinel上使用SENTINEL MASTER mastername命令,观察输出。

移除旧的master节点或者不可达的salve节点

Sentinel绝不会忘记一个给定master节点的slave节点,即使它们已经很长时间处于不可达的状态。这种策略是非常有用的,因为Sentinel应该有能力正确的重新配置一个在网络分片或者发生故障之后重新连接的slave节点。除此之外,发生故障转移之后,发生故障的master节点沦为slave节点,这种方式使得只要其再次可用时能够尽可能快的重新对其配置。

但是在某些情况下,你想要通过Sentinel将其监控下的某个slave节点永久性的移除。为了达到这个目的,你需要发送一个SENTINEL RESET命令给所有的Sentinel:所有的Sentienl将会在10秒钟之内刷新slave节点列表,然后只添加当前master节点INFO命令返回结果列表中的正确的slave。

发布/订阅消息

Sentinel是Redis兼容的Pub/Sub服务器,客户端可以使用Sentinel来SUBSCRIBE或者PSUBSCRIBE一个channel或者获取指定事件的消息通知。(但是不能使用PUBLISH)。 channel的名称与事件的名称是一致的。举个例子:channel名称为“+sdown”将会接受到所有SDOWN实例发出的通知。(SDOWN命令的含义是该实例将从你所查询的Sentinel下处于不可达的状态)。 如果想要接收到所有消息,可以使用“PSUBSCRIBE *”实现。 下面给出当时用该API时,返回数据的给事,第一个参数是channel/event的名称,其余参数是数据的格式化输出。 注意:instance details 指明的地方,其channel的返回信息中包含了以下用于区别目标实例的内容。
<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>
上面的命令中从@参数到结尾来标明一个master节点,并且这些内容是可选的,而且只有在当前实例不是master节点本身时才需要特殊指明。
  • +reset-master<instance details>:master服务器已经被重置。
  • +slave<instance details>:一个新的slave服务器已经被Sentinel发现并链接。
  • +failover-state-reconf-slaves<instance details>:故障转移状态切换到reconf-slaves状态。
  • +failover-detected<instance details>:另一个Sentinel还是故障转移,或者其他任何额外的实体被发现(一个slave节点变为master节点)。
  • +slave-conf-sent<instance details>:领导(Leader)Sentinel向实例发送了SLAVEOF命令,为其设置新的master服务器。
  • +slave-reconf-inprog<instance details>:当前实例正在将自己设置为指定master节点的salve,但是相应的同步过程尚未完成。
  • +slave-reconf-done<instance details>:slave节点已经完成对新的master节点的同步。
  • -dup-sentinel<instance details>:对于给定master节点进行监控的一个或多个Sentinel已经因为重复出现而被移除(这种情况发生在Sentinel示例重启的时候)。
  • +sentitnel<instance details>:对指定master节点的新Sentinel已经被识别并添加。
  • +sdown<instance details>:指定的实例现在处于主观下线状态(Subjectively Down)。
  • -sdown<instance details>:指定的实例不再处于主观下线状态。
  • +odown<instance details>:指定的实例现在处于客观下线状态(Objectively Down)。
  • -odown<instance details>:指定的实例不再处于客官下线状态。
  • +new-epoch<instance details>:当前的纪元(epoch)已经被更新。
  • +try-failover<instance details>:新的故障转移进程正在执行中,等待被大多数的Sentinel选中。
  • +elected-leader<instance details>:赢得指定纪元的选举,可以进行故障转移操作了。
  • +failover-state-select-slave<instance details>:故障转移操作所处的状态是select-slave状态--Sentinel正在寻找可以升级为master节点的slave节点。
  • no-good-slave<instance details>:Sentinel未能找到合适进行升级的slave节点。Sentinel会在一段时间之后再次尝试寻找合适的slave节点来进行升级,或者直接放弃故障转移操作。
  • selected-slave<instance details>:Sentinel找到了适合进行升级的slave节点。
  • failover-state-send-slaveof-noone<instance details>:Sentinel正在将指定的slave服务器升级为master,等待升级操作完成。
  • failover-end-for-timeout<instance details>:故障转移因为超时而中止,不过所有slave节点最终都会开始复制新的master节点。
  • failover-end<instance details>:故障转移操作顺利完成,所有slave节点开始复制新的master节点。
  • switch-master<master name><oldip><oldport><newip><newport>:配置变更,master节点的IP和地址已经改变。这时绝大多数外部用户都关心的信息。
  • +tile:进入tilt模式。
  • -tile:退出tile模式。

处理-BUSY状态

当Lua脚本的运行时间超过指定时间限制时,Redis就会返回-BUSY错误。 当出现这个情况时,Sentinel在尝试执行故障转移之前,会先向服务器发送一个SCRIPT KILL命令,如果服务器正在执行的是一个只读脚本的话,那么这个脚本就会被杀死,服务器就会回到正常状态。 如果这个服务器在重试操作之后仍然处于错误的状态,其最终会被执行故障转移。

slave节点的优先级

Redis实例的配置参数中有一个称为slave-priority。这个参数会在Redis slave节点的INFO命令输出中显示,并且Sentinel会使用这个参数来选定一个可以进行故障转移的节点:
  1. 如果slave的优先级被设置为0,那么其将不能被提升为master节点。
  2. 该参数的值越小,优先级越高,更容易被Sentinel选中进行故障转移。
举个例子:假设在当前数据中心里存在一个S1节点是当前master节点的slave,在另一个数据中心中存在S2节点。可能发生的情况是S1设置优先级为10,S2设置优先级为100,因此如果master节点发生故障转移,虽然S1,S2都是可用的,但是S1将会选为提升为master的节点。 更多关于slave节点如何被选举的内容,我们将在下文slave选举与优先级中详细描述。

Sentinel和Redis授权

当master节点配置为客户端需要提供password才能访问时,作为一种安全措施,slave节点也需要提供该password来实现master-slave之间的授权链接,这样才能使用异步复制协议。 这种方案可以通过下面的步骤来实现:
  • master节点中配置requirepass参数,设置授权密码,确保拒绝接受未授权客户端的访问。
  • slave节点中配置masterauth参数,来授权链接到master节点,实现正确的从master节点复制数据。
当使用Sentinel,将不再有单一的master节点,因为在故障转移之后,slave节点将会提升为master角色,原来的master节点可能会被重新配置为slave,因此你要做的就是,在上面的所有实例中都配置该参数,无论是master节点,还是slave节点。这是通常情况下是一个正确的配置,只要你不想只保护在master上的数据,还有可连接的slave上存在的相同的数据。 但是,在某些少数情况下,你可能需要一个没有授权的slave节点,此时,你然这么配置,并且通过设置slave优先级为0,来保护该slave节点不被提升为master,在salve中只配置masterauth参数,而不使用requirepass参数。这样,数据就能够被未授权的客户端进行只读操作。

Sentinel客户端的实现

Sentinel需要现实的客户端支持,除非,系统被配置为执行一个用来进行透明的重定向所有到新的master节点的请求的脚本(虚拟IP或者其他类似的系统)。客户端库实现的主要内容将在本文Sentinel客户端指南中进行介绍。

更多高级的概念

在本节,我们将介绍一些Sentinel工作原理的细节,但是具体的实现与算法将会在本文的最后在进行介绍

主观下线(SDOWN)和主观下线(ODOWN)失败状态

在上文中已经使用了这两个名词,现在我们详细介绍这两个概念: Redis Sentinel有两个关于下线不同的概念:
  • 主观下线(Subjectively Down):指的是单个Sentinel实例对于服务器做出的下线判断。
  • 客观下线(Objectively Down):指的是多个Sentienl实例(至少达到quorum参数的配置)在对同一个服务器做出SDOWN判断,并且通过SENTINEL is-master-down-by-addr命令相互交流之后,得出的服务器下线判断。
从Sentinel的角度出发,如果一个服务器没有在master-down-after-milliseconds选项所规定的时间内,对接收到的PING命令没有正确返回的话,那么Sentinel就会将这个服务器标记为主观下线。 服务器对于PING命令的有效返回可以是以下三种回复的其中一种:
  • 返回+PONG
  • 返回-LOADING错误
  • 返回-MASTERDOWN错误
任何其他类型的回复,或者没有回复都会被视为无效。但是,注意:一个在合法的master节点在INFO命令的输出中将自己声明为slave将会被视为下线。 注意:一个服务器必须在master-down-after-milliseconds时间内,一直返回无效回复才会被Sentinel标记为主观下线(SDOWN)。举个例子:如果时间配置为30000毫秒(30s),那么只要服务器能在每29秒内返回至少一次有效恢复,这个服务器就仍然会被视为处于正常状态。 主观下线还不足以触发一次故障转移:换句话说,只有一个Sentinel相信一个Redis实例不可用。为了触发一次故障转移,必须达到客观下线的条件。 从主观下线切换到客观下线没有使用严格的法定人数算法,而是使用了gossip协议:如果Sentinel在给定的时间范围内,从其他Sentinel哪里接受到了足够属相的master服务器下线的报告,那么Sentinel就会将master服务器的状态从主观下线改变为客观下线。如果之后其他Sentinel不再报告master服务器已下线,那么客观下线状态就会被移除。 在实际的多数节点中使用一个更严格的授权策略,为的是真正的启动故障转移的过程,对应的,如果没有达到客观下线的状态就绝不会触发故障转移过程。 客观下线条件只适用于master服务器。对于其他类型的Redis实例,Sentinel将他们判断为下线状态时就不需要进行协商,所以slave节点或者其他Sentinel永远都不会达到客观下线的条件。 然而,只要一个Sentinel发现某个master节点进入客观下线状态,这个Sentinel就可会被其他Sentinel推举出来,并对失效的master节点进行故障转移操作。

Sentinel与Slave 自动发现

一个Sentinel可以与其他多个Sentinel进行连接,各个Sentinel之间可以互相检查对方的可用性,并进行信息交换。所以,你不需要为运行的每个Sentinel分别设置其他Sentinel的地址,因为Sentinel使用Pub/Sub功能来自动发现正在监视相同服务器的其他Sentinel。该功能是通过向一个称为"_sentinel_:hello"的channel中发送消息来实现的。 类似的,你也不需要手动的列出master节点下的slave节点,因为Sentinel可以通过询问master节点来获得所有slave节点的信息。
  • 每一个Sentinel会以每2秒一次的频率,通过发布与订阅功能,向被它监视的所有master节点和slave节点的“_sentinel:hello_”频道发送一条消息,信息中包含了Sentinel的IP地址(ip),端口号(port),和运行ID(runid)。
  • 每一个Sentienl都订阅了被他监控的所有master节点和salve节点的“_sentinel:hello_”频道,查找之前从未出现过的sentinel。当一个Sentinel发现了一个新的Sentinel时,它会将新的Sentinel添加到一个列表中,这个列表保存了Sentinel已知的,监视同一个master节点的其他所有Sentinel。
  • Sentinel发送的hello信息中还包括完整的master节点的当前配置。如果一个Sentinel包含的master服务器配置比另一个Sentinel发送的配置要旧,那么这个Sentinel会立即升级到新配置上。
  • 在将一个Sentinel添加到监视master服务器的列表上面之前,Sentinel会先检查列表中是否已经包含了和要添加的Sentinel拥有相同ID或者相同地址(IP,PORT)的Sentinel,如果是的话,Sentinel会先移除列表中已有的那些拥有相同运行ID或者相同地址的Sentinel,然后再添加新Sentinel。

Sentinel在非故障迁移的情况下对实例进行重新配置

即使没有自动故障迁移操作在进行,Sentinel总会尝试将当前的配置设置到被监视的实例上面。 特别是:
  • 根据当前的配置,如果一个slave节点被宣告为master节点,那么它会替代原有的master节点,成为新的master,并且成为就的master节点的所有slave对象节点的复制目标。
  • 那些链接了错误master节点的slave会被重新配置,使得这些slave节点去复制正确的master节点。
为了实现通过Sentinel重新配置slave,错误配置信息必须观察足够长的时间,这要比使用广播通知新配置花费的时间要长。这种做法阻止带有状态配置信息的Sentinel(如:刚刚重新从分片网络加入进来的Sentinel)在接收到更新之前,尝试改变slave节点的配置信息。 同时,注意:
  • master节点从失效变为重新可用时,会被重新配置为slave。
  • 在网络分片期间,被划分的slave节点一旦重新连接就会被重新配置

本节内容最重要的概念是:Sentinel是一个总是尝试将最新的合法配置传递给其监控的集合中所有对象的系统。

Slave选举与优先级

当Sentinel的一个实例准备执行一次故障转移时,此时,对应的master节点已经处于客观下线(ODOWN)并且当前Sentinel已经从已知的多数Sentinel中获得授权,一个合适的slave对象需要被选举出来。 该slave节点的选举过程通过以下信息进行选择评估:

  1. 与master节点的失联时间Disconnection time from the master
  2. slave的优先级Slave priority
  3. 复制偏移量(Replication offset processed
  4. 运行ID(Run ID

一个slave节点,被发现与master节点失联超过10于配置的down-after-milliseconds-option时间,加上从准备执行故障转移的Sentinel上主观下线的时间,被视为不适合进行故障转移操作。接着会选择下一个slave节点并判断条件。

在更严格的条件下,一个slave节点的INFO命令输出提示其与master节点失联的时间超过:

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

会被视为是不可靠的,并且彻底的不考虑使用该节点。

Slave的选取范围只考虑通过上面测试的那些节点,并且按照上面的标准进行排序。具体如下:

  1. Slave节点按照redis.conf文件中配置的slave-priority参数的值进行排序,值越小,优先级越高。
  2. 如果优先级相同,那么检查复制过程的复制偏移量,并且接受到数据越多的slave节点优先级越高。
  3. 如果上面两个比较项都是相同的话,那么就执行一个更加深入的比较:Run ID,按照字典顺序选择Run ID较小的那一个。slave拥有较小的Run ID并不是一个真正的优势,但是,这个对slave的选举过程更加具有决定性的作用,而不是在排序之后随机选择一个。

如果有些实例将被必须被选择的话,Redis的master节点(可能在发生故障转移之后变为slave),slave节点,其都必须配置salve-priority参数项。否则,所有的实例都会使用默认的Run ID来运行。

Redis实例也可以对slave-priority参数配置为一个特殊的值:0,这么做的目的是保证该节点绝不会被Sentinel选中为新的master节点。然而,就算一个slave节点的优先级参数被设置为0,其将还是会被Sentinel重新配置从而使其在发生故障转移之后能够从新的master节点进行备份,唯一的区别就是:该slave本身不会成为master选举的备选对象。

算法及内部原理

在本节,我们将探索更多Sentinel行为的细节实现。对Redis的用户而言,并不是必须要学习所有的细节内容,但是一个深入的学习与理解能够帮助我们更加正确的方式部署运行Redis。

Quorum

在前面的小节中,我们说每个被Sentinel监控的master节点都配置了quorum参数。其指明了判断一个master节点不可用或者进入错误状态并触发一次故障转移所需要的最小Sentinel数量。 但是,在故障转移触发之后,为了保证故障转移真正的执行,至少需要超过半数的Sentinel节点授权一个Sentinel来执行故障转移。Sentinel绝不可能在一个有少数节点的分片网络中执行故障转移操作。 下面,我们来让整个故障转移的过程更加清晰一些:
  • Quorum:发现master节点进入错误状态的Sentinel数量,达到该数量时会被认为客观下线。
  • 客观下线状态将会触发故障转移操作。
  • 一旦故障转移触发,监控该master节点的Sentinel会尝试从多数Sentinel中获得授权并尝试故障转移。(或者如果quorum参数设置的值大于半数Sentinel数量,此时就需要按照quorum条件判断,即两者中取最大数)。
这两种方式看起来多余的,但实际上非常的容易理解和使用。举个例子:如果此时有5个Sentinel,quorum参数值为2,那么故障转移操作将会在只要2个Sentinel同意该master节点失效时触发,但是这2个Sentinel的其中一个只有获得至少3个Sentinel授权之后真正的故障转移才会开始。 如果将quorum参数配置为5,那么所有的Sentinel都必须同意master节点是错误状态,所有节点都授权才能开始故障转移操作。 换句话说,quorum参数有两种改变Sentinel运行方式的方法:
如果设置quorum的值小于部署的Sentinel中的半数,我们可以使Sentinel对故障转移更加敏感,即只要达到quorum,就算只有少数节点同意,不用与master节点商量,就可以触发故障转移。(仅仅是触发)
如果设置quorum的值大于部署的Sentinel中的半数,那么只有当处于正常连接的Sentienl中超过quorum数量的Sentinel同意master下线才能触发故障转移操作。

配置纪元(Configuration epochs)

Sentinel需要从多数Sentinel中获得授权才能开始故障转移操作的主要原因如下: 当一个Sentinel是授权了,其获得了对失效master节点的一个唯一的纪元配置(configuration epoch)。这是一个数值类型的值,其将被用来在故障转移完成之后来确定配置的版本号。因为,多数Sentinel同意分配一个指定的版本给一个指定的Sentinel,其它Sentinel是无法使用这个号码的。这意味着:每一个故障转移的配置都被唯一的版本号标识。接下来,我们看看这种做法为什么是非常重要的。 而且Sentinel还有一个规则:如果一个Sentinel为另一个负责故障转移指定master节点的Sentienl进行投票的话,这个Sentinel将会等待一段时间来再次尝试故障转移相同的master节点。这个时间可以在sentinel.conf配置文件中的failover-time选项进行配置。换句话说,Sentinel将不会对相同的失效节点做两次尝试操作,第一次获得授权之后将进行尝试操作,如果失败了,将转由另一个Sentinel进行尝试,以此类推。 Redis Sentinel保证转移过程具有活跃性(liveness property):如果Sentienl中的大多数能够正常交流,那么最终将会授权给一个Sentinel来负责故障转移一个失效的master节点。 Redis Sentinel也保证转移过程具有安全性(safety property):每一个对相同失效节点进行故障转移的Sentinel都会拥有唯一的纪元配置。

配置传播

一旦Sentinel成功的对master节点进行故障转移,其将会开始将新的配置信息广播通知给其他Sentinel,以此使得其他Sentinel对给定的master节点进行配置信息的更新。 对故障转移认定为是成功的条件,其要求:Sentienl有能力发送SLAVE NO ONE 命令给选择出的salve节点,且在之后切换为master节点,同时该节点能够在master节点的INFO命令输出中显示出来。 从这点出发来看,即使slave节点重新配置正在进行中,故障转移也被认为是成功的,并且所有的Sentinel被要求开始报告新配置。 这种新配置信息的转播方式就是我们为什么需要每一个Sentinel故障转移时需要一个唯一版本的原因。(即配置纪元)。 每一个Sentinel都会使用Redis的发布/订阅消息模式持续的广播通知master节点的配置的版本信息,包括master中的所有slave节点。与此同时,所有Sentienl会等待其他Sentinel声明的配置消息。
  • 配置信息在“_sentinel_:hello”channel上发布/订阅消息。
因为每一个配置都拥有唯一的版本号,因此,版本号越大的配置总是会取代版本号小的配置。 举个例子:名称为mymaster的master节点的配置启动后,所有的Sentienl都认为mymaster节点的地址为:192.168.1.50:6379.这份配置的版本号为version 1.在一段时间之后,其中一个Sentinel获得授权并开始故障转移,此时版本号位version 2.如果故障转移成功,其将会广播通知这份新的配置,192.168.1.50:9000,版本号为version 2。其他所有的示例将会接受到这份配置,并且按照其内容更新自身的配置信息,因为version 2有更大的版本号。 换句话说,Sentienl保证活跃性:Sentinel的集合中有能力进行交流,并将所有相同的配置收敛到更高版本号的配置上。 如果发生网络分片,那么每一个网络分片内部都会收敛到内部的更高的版本上。在没有网络分片的情况下,这里只有一个简单的网络区域,那么所有Sentienl都会同意一个配置。

分片网络下的一致性

Redis Sentinel配置是最终一致性的,因此,每一个分片网络中将会收敛到更高的可用配置上。但是,实际项目中使用Sentinel的系统中有3种不同的角色:
  • Redis 实例
  • Sentinel 实例
  • 客户端 实例
我们需要考虑上面全部的3种角色来定义系统系统。 下面是一个非常简单的网络模型,每一个box中都运行一个redis 实例,sentinel实例: NoSQL之Redis---哨兵(sentinel)
此时系统的初始状态为:Redis 3是master节点,Redis 1,Redis 2是slave节点。发生网络分片时,将master节点隔离到系统外。Sentinel 1和Sentinel 2开始故障转移过程,将Redis 1提升为新的master节点。 Sentinel的特性保证:Sentinel 1 ,Sentinel 2 都拥有关于master节点的最新配置。但是Sentinel 3 由于处于分片网络中,所以仍然保持旧的配置。 我们知道Sentinel 3将会在网络分片结束时获得配置更新,但是,在分片过程中,仍然链接到Sentinel 3,Redis 3网络的Client将会发生什么事情呢? Client 3将会仍然向旧的master节点,Redis 3写入数据。当网络分片结束时,Redis 3将会转变为Redis 1的slave节点,所有在分片过程中写入的数据都会丢失。 取决于你的配置,你可能想要或者不想要下面的情况发生:
  • 如果你使用Redis作为缓存,那么可能是分方便的,结果是:Client B仍然有能力向旧的master节点写入数据,即使这些数据最后都会丢失。
  • 如果你使用Reids作为存储,这是不好的做法,并且你需要配置系统参数使得一定程度上能够避免这类情况发生。
因为Redis采用异步复制的策略,所以没有办法从根本上彻底阻止这种情况下的数据丢失,但是你可以使用下面的配置选项,绑定Redis3,Redis 1它们之间的差异:
min-slaves-to-write 1
min-slaves-max-lag 10
当一个master节点使用上面的配置是(更多内容请参考示例配置redis.conf文件中的解释),如果发生分片,其没有至少一个slave可以写,那么其将会停止接受写操作。因为复制是异步的,停止接受写操作,意味着slave节点失去了链接,或者没有在指定的max-lag时间内回复异步确认的消息。
Redis 3使用上面的配置示例,将会在10s之后变为不可用状态。当网络分片结束后,Sentinel 3配置将会统一成一份,并且Client B将会有能力获取一份可用的配置并继续服务。 在通常情况下,Redis+Sentinel作为一个整体部署,是一个最终一致性的系统,其在故障转移之后配置信息的版本最终会合并统一,并且将来自原来master节点上的数据删除,并复制当前master节点上的数据,因此,总是存在着丢失已经接收的写命令的时间段。这是因为Redis采取了异步复制的策略和系统的“虚拟”合并配置功能的丢失特性。 注意:这对Sentienl本身而言没有造成任何限制,并且如果你将复制状态的机器的强一直性和故障转移结合在一起,那么同样的配置仍然需要使用。以下是两种方法来避免丢失接受的写命令:
  • 使用同步复制(并且采取适当的一致性算法来运行复制状态的机器)
  • 当存在多个不同版本的,可以合并的,相同对象时,使用最终一致性的系统设计
Redis目前还无法使用上面的两种设计方案,并且Redis正在这两个之外的目标上发展。但是,在Redis之上存在着使用方法2的替代的方案,如,SoundClound Roshi,Netfix Dynomite。

Sentinel 状态的持久化

Sentinel的状态会被持久化在Sentinel的配置文件中。如:每当Sentinel接受到一份来自新的master节点的配置信息时,或者为其创建一份配置文件时,该配置就会和配置纪元一起被持久化到磁盘上。换句话说:停止或者重启启动Sentinel进程都是安全的。

TILT 模式

Redis Sentinel严重依赖于计算机的时间功能:比如,为了判断一个节点实例是否可用,Sentinel会记录下最后一次成功相应PING命令的时间,并且与当前时间进行比较,以此来判断该实例的存活时间。然而,如果计算机系统时间发生非正常情况下的变化,或者计算机系统非常忙碌,又或者因为某些原因进程被阻塞时,Sentinel可能会跟着出现故障。TILT模式是一种特殊的保护模式:当Sentinel发现异常,并且觉察到可能降低系统可用性时,就会进入TILT模式。由于Sentinel的时间中断器默认每秒执行10次,所以我们预期两次之间的执行间隔为100毫秒左右。Sentinel的做法是:记录上一次时间中断器执行时的时间,并将它和本次时间中断器执行的时间进行对比:如果两次调用时间的差值为负,或者非常大(超过2s钟),那么Sentinel进入TILT模式。如果Sentinel已经进入TILT模式,那么Sentinel延迟退出TILT模式的时间。当Sentinel进入TILT模式时,它仍然会继续监视所有目标,但是:
  • 它不再执行操作。
  • 当有实例向这个Sentinel发送SENTINEL is-master-down-by-addr命令时,Sentinel返回负值:因为当前Sentinel所进行的下线判断已经不再准确。
如果TILT可以正常维持30s,那么Sentinel退出TILT模式。注意:在某些情况下,能够使用内核提供的单一时钟来替代TILT模式。但是,还不清楚的是,这是不是一个好的解决方案,因为当前系统避免了在进程刚刚被调度或者被调度之后没有执行足够的时间情况下的问题。--------------------------------------------------------------------------------------------------------------------------------------------------------至此,NoSQL之Redis---主从复制特别备注:本篇内容有很多地方翻译不够准确,建议各位看官看的时候最好对比官方文档一起看。如果发现不合理的地方,还请积极留言。参考资料:

官方文档:http://redis.io/topics/sentinel