本文翻译自官网文档(https://redis.io/docs/manual/sentinel/)
redis sentinel提供了非cluster模式的高可用部署
redis sentinel同时附带提供监控、通知功能,以及为客户端充当配置提供器。
在宏观视图上,sentinel提供如下功能列表:
监控,sentinel持续监控master和replica是否如期望正常工作
通知,通过API,sentinel可以通知系统管理员或其他应用程序,某个Redis实例发生了异常
自动故障转移,如果一个master没如期望正常工作,sentinel将在被推举为新master的replica节点上启动一个故障转移进程,其他的replica节点将被重新配置使用最新的master,使用redis server的应用程序会被通知新的master地址
配置提供器,sentinel为客户端扮演了服务发现的权威,客户端连接到sentinel询问当前负责提供服务的master的地址,如果发生了故障转移,sentinel将报告新的地址
分布式系统Sentinel
redis sentinel是一个分布式系统
sentinel被设计成多进程一起合作的方式来运行,多进程合作的好处在于:
当多个sentinel同意某个master不可用时,失效检测将会被执行,这将降低误报的概率
即使不是所有的sentinel进程在工作,sentinel依然能够正常发挥作用,这使系统在应对失效方面变得更加鲁棒。毕竟,拥有一个会单点失效的故障转移系统一点都不好玩。
sentinel、redis实例(master和replica)以及连接sentinel和redis的客户端组成了一个更大的具有独特特性的分布式系统。在这个文档中,将从最基本的信息开始逐步介绍其概念,以帮助理解sentinel基本特性,并讲解一些更复杂的信息(可选部分)以理解sentinel如何工作。
Sentinel quick start
获取Sentinel
当前sentinel版本称之为Sentinel 2,它使用更加强大且简洁的预测算法(本文档中将解释),是最初sentinel实现的重写版。
Redis Sentinel的稳定版是从Redis 2.8开始发布上市。
新开发的内容在非稳定分支中进行,且有时新特性在被评估为稳定的话也会回植到最新的稳定版中。
随Redis2.6发布的Redis Sentinel V1已经废弃,且不应再使用了。
运行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默认监听tcp端口26379,所以如果要sentinel能正常工作,你服务器的端口26379必须处于打开状态,用来接收其他ip地址下的Sentinel实例发来的连接。否则Sentinel就不能就将要做什么进行沟通和投票,因此故障转移也将永远不会执行。
部署前需要知道的基础
- 你需要至少3个Sentinel实例来形成一个鲁棒的部署
- 这三个Sentinel实例应运行在确信会以隔离方式失效的计算机或虚拟机中,因此,比如在不同可用区域的物理服务器或虚拟机
- Sentinel+Redis分布式系统不保证在失效期间仍然保留已经获得承诺的写活动,因为Redis使用异步复制。尽管如此,仍然有一些部署方式,让丢失写的窗口期限制在一定时间内,同时有一些其他低安全的方式来部署它。
- 你需要在你的客户端中支持Sentinel,流行的客户端lib都支持Sentinel,但是并非所有。
- 如果你不在开发环境一次次去测试它,就不会有一个安全的HA,甚至如果可以的话,甚至在生产环境也要进行一次次测试。你可能会有一个很晚(早上3点,当你的master停止工作时)才将显现出来的配置错误。
- Sentinel、Docker或者其他形式的NAT或端口映射需要谨慎混用。Docker执行端口重映射,破坏Sentinel去自动发现其他Sentinel进程以及master的副本列表。阅读该文档的"Sentinel,Docker,NAT,可能的问题"小节以获取更多信息。
配置Sentinel
Redis源码发行版中包含了一个名为sentinel.conf的文件,这是一个自文档化的样例配置文件,你可以使用它来配置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(不管有多少个副本)一个不同的名称。不需要指定副本,他们将会被自动发现。Sentinel会将副本的额外信息自动更新到配置文件(为了保留信息,以防重启)。每次在故障转移中一个副本被推举为master时,每次一个新的Sentinel节点被发现时,配置都将会被重写。
上面的配置样例监听了两组Redis实例,每组包含一个master和未给定数量的副本。一组实例被称之为mymaster,另一个称之为resque。
sentinel monitor语句的参数意义如下:
sentinel monitor <master-group-name> <ip> <port> <quorum>
为清楚起见,让我们逐行检查这些配置的含义:
第一行是用来告诉Redis去监视一个叫mymaster的master,该master地址是127.0.0.1 且端口号是6379,且quorum为2。所有都非常显然易见,但是quorum这个参数:
- quorum 是同意master不可达为事实的Sentinel实例需要达到的个数,以此来实际标记master为失效状态,如果有可能,最终将启动故障转移过程
- 尽管如此,quorum只是用来检测失效。为了切实执行故障转移,某个Sentinel实例需要被选举为leader且被授权进行故障转移。这只会在多数Sentinel进程的投票下才会发生。
所以,举例来说如果你有5个Sentinel进程,且给定master设置的quorum为2,这将发生如下情况:
- 如果两个Sentinel实例同时同意master不可达,两者之一将试图启动故障转移
- 如果至少有3个Sentinel实例可达,故障转移将被授权且会真正执行。
事实上,这意味着失效期间,如果多数Sentinel进程无法沟通,Sentinel绝不会启动故障转移(也即故障转移不会发生在少数分区中)。
Sentinel其他选项
其他选项几乎都以以下形式出现:
sentinel <option_name> <master_name> <option_value>
而且以一下目的来使用:
- down-after-milliseconds 是一个实例不可达(不管是无法回复我们的ping还是回复了一个错误)的以毫秒为单位的时间,Sentinel实例以此时间来断定master已经崩溃
- parallel-syncs 一个故障转移后,可被重新设置同时使用新master的副本数,这个数越小,故障转移所花费的时间就越多,尽管如此,如果这些副本被配置为为老数据提供服务,你也许不会想让所有的副本同时和master重新同步。尽管复制过程几乎都是非阻塞的,还是会在从master下载大数据的时候停止。你也许要设置其值为1,以确保同一时间只有一个副本不可达。
额外的选项在这个文档的其他部分描述,且已经在源码发布版里的样例配置文件sentinel.conf中说明。
配置参数可在运行时修改
- 针对master的配置参数修改使用SENTINEL SET
- 全局配置参数的修改需要使用SENTINEL CONFIG SET
阅读 Reconfiguring Sentinel at runtime 小节以获取更多信息
Sentinel部署示例
现在你已经知道了Sentinel的基本信息,你也许想知道在哪安放你的Sentinel进程,你需要多少个Sentinel进程,诸如此类的问题。在这节将展现一些部署样例。
我们使用ASCII字符图形艺术来向你展现配置示例,以下是不同符号所表达的内容:
+--------------------+
| 这是个失效独立的 |
| 机器或虚拟机 |
| 我们称之为 |
| “盒子” |
+--------------------+
我们在盒子里写上它里面运行着什么
+-------------------+
| Redis master M1 |
| Redis Sentinel S1 |
+-------------------+
不同的盒子通过一条线连接,来表达他们之间可以相互沟通
+-------------+ +-------------+
| Sentinel S1 |---------------| Sentinel S2 |
+-------------+ +-------------+
用斜杠中断连接线来表达网络分区
+-------------+ +-------------+
| Sentinel S1 |------ // ------| Sentinel S2 |
+-------------+ +-------------+
同时注意:
- Master主节点称之为M1 M2 M3 ...Mn
- Replica副本节点称之为R1 R2 R3...Rn(R代表副本)
- Sentinel哨兵称之为S1 S2 S3...Sn
- Client客户端称之为C1 C2 C3...Cn
- 当Sentinel的行为导致一个实例的角色发生变化时,我们把它放在方括号[]里,所以[M1]意思是由于Sentinel的介入,某个实例现在成为了Master主节点
注意我们永远不会展现只有两个Sentinel的设置方式,因为为了达到故障转移,Sentinel需要与多数哨兵进行沟通
示例1:只有两个哨兵,不要这么做
+----+ +----+
| M1 |---------| R1 |
| S1 | | S2 |
+----+ +----+
配置: quorum = 1
- 在这个设置下,如果Master M1失效,R1将会被推举,因为两个哨兵能够对失效(显而易见,quorum设置为1)达成一致,而且能授权启动故障转移,因为大多数的Sentinel是2。所以,它表面上看起来能够工作,尽管如此,继续往下看,来看看为什么这个设置会失败。
- 如果M1所在的盒子停止工作,S1也停止工作。运行在另一个盒子里的Sentinel实例S2就无法授权启动故障转移,所以这个系统将变为不可以。
注意,不同的故障转移需要“大多数”来进行排序,以及需要“大多数”来向所有Sentinel实例传播最终的配置,也要注意在上面那种设置下,没有达成一致情况下的单边故障转移能力是非常危险的:
+----+ +------+
| M1 |----//-----| [M1] |
| S1 | | S2 |
+----+ +------+
上面的配置中我们以完美对称的方式创建了2个Master(假定无授权下S2能够故障转移)。客户端也许能够无限的向两边写入,且当分区恢复后,就没有办法理解哪边的配置才是正确的。
所以,请至少在三个不同的盒子里部署三个Sentinel实例。
示例2:三个盒子下的基本设置
这是一个非常简单的设置,它具有简单协调就能达到额外安全性的优势。它建立在三个盒子的基础上,每个盒子运行这一个Redis进程和一个Sentinel进程。
+----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+
配置: quorum = 2
如果Master M1失效,S2和S3将对失效达成一致,且能够为故障转移进行授权,让客户端能够正常继续工作。
对于每种Sentinel设置,因为Redis使用异步复制,总是会有丢失写的风险,因为一个已确认的写可能还没到达那个将被推举为Master的副本。在上面那个设置中,存在一个较高的风险,客户端和老Master节点被分裂到了一起,如下图所示:
+----+
| M1 |
| S1 | <- C1 (写将会丢失)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+
这种场景下,一个网络分区将老的Master节点M1隔离,所以副本R2被推举为Master。经管如此,客户端如C1和老Master节点在同一个分区,这个客户端将继续向老Master节点写入数据。这些数据将会永久丢失,因为当分区恢复,老Master会被重新配置为新Master的一个副本,它将丢弃自己的数据集。
这个问题可以使用如下的Redis复制特性来减轻,如果一个Master检测到它无法将写传输到特定数目的副本,它将允许停止接受写入。
min-replicas-to-write 1
min-replicas-max-lag 10
通过以上的配置(请查看Redis分发版中的自注释样例文件redis.conf以获取更多信息),一个担当为Master角色的Redis实例,将在它无法写入至少一个副本的情况下,它将停止接受写入。因为复制是异步的,无法写入实际上意味着副本无法连接或者在max-lag秒范围内没有向我们发送异步确认。
使用这个设置,以上示例中的老Redis Master节点M1将在10秒后变的不可用,当分区恢复,Sentinel配置将覆盖这个新节点,客户端C1将能拉取到一个合法的配置且能和新的Master节点一起继续正常工作。
但是,天下没有免费的午餐,在这个改良下,如果这两个副本崩溃了,Master将停止接受写入。这是一种权衡。
示例3:Sentinel部署在客户端盒子中
有时我们只有两个Redis盒子可用,一个用来部署Master一个用来部署副本。在那种情况下,示例2中的配置就是不可行的了,所以我们可以使用以下方法,即在客户端所在区域部署Sentinel:
+----+ +----+
| M1 |----+----| R1 |
| | | | |
+----+ | +----+
|
+------------+------------+
| | |
| | |
+----+ +----+ +----+
| C1 | | C2 | | C3 |
| S1 | | S2 | | S3 |
+----+ +----+ +----+
配置: quorum = 2
在该设置下,看待Sentinel的角度等同于客户端:如果一个Master对于多数客户端来说都可达,那一切安好。C1 C2 C3是普通客户端,那并不意味着C1唯一标识某个连接到Redis的特定客户端,它更可能是一个应用服务器、一个Rails app或类似的东西。
如果M1和S1运行所在的盒子失效,故障转移将会发生,这不会有什么问题,但是,显而易见的,不同的网络分区情况将导致不同的行为。比如,如果客户端和Redis server之间网络无法连接,Sentinel将无法有效设置,因为Redis Master和副本将同时不可用。
注意,如果C3和M1发生分裂(上面描述的网络不太可能发生,但是在不同的网络布局下或者因为软件层的失效则有可能会发生),我们将碰到一个类似示例2中描述的问题,不同之处在于,我们无法打破对称性,因为只有一个副本和master,所以,当Master和副本连接不上,它将不能停止接受查询,否则在副本失效期间,master将永远不可用。
所以这是一个有效的设置,但是示例2中的设置有些优势,比如Redis HA系统和Reids运行在相同盒子里管理起来就更加简单,以及限制少数派分区中的master接收写入时间的能力。
示例4:少于三个客户端
如果客户端侧少于3个盒子(比如3个web server),示例3中描述的设置是不能使用的。在这种情况下,我们需要使用如下混合设置:
+----+ +----+
| M1 |----+----| R1 |
| S1 | | | S2 |
+----+ | +----+
|
+------+-----+
| |
| |
+----+ +----+
| C1 | | C2 |
| S3 | | S4 |
+----+ +----+
配置: quorum = 3
这有点类似示例3中的设置,但是这里我们在4个盒子里运行了4个Sentinel实例,如果Master M1变的不可用,其他三个Sentinel实例将执行故障转移。
理论上,移除C2和S4运行所在的盒子并把quorum设置为2,这个设置也能正常工作。然而,我们不太可能只需要Redis侧的HA,但在应用层却不要HA。
Sentinel, Docker, NAT,可能的问题
Docker使用了一个称之为端口映射的技术:运行在Docker容器里的程序,可能会以不同于程序认为正在使用的那个端口向外暴露,这点很有用,可以达到在相同服务器里多个容器同时使用相同的端口。
Docker并不是会产生这种情况的唯一软件系统,还有其他的端口会被重映射的NAT设置,有时,不仅是端口,ip地址也会被映射。
端口和地址重映射从两个方面对Sentinel产生问题:
Sentinel自动发现其他Sentinel不能正常工作,因为这建立在每个Sentinel声明自己所监听的端口和ip的hello消息的基础上。但是Sentinel没办法理解被重映射后的地址或端口,所以这就声明了一个其他Sentinel试图连接时所采用的错误信息。
-
副本会以相似的方式被罗列在Redis Master的INFO输出信息中:Master检查远程TCP连接来侦测地址是否可用,同时端口是由副本在握手时给定,但是同1中揭露的相同原因,端口可能是错误的。
因为Sentinel使用Master的INFO输出信息来自动侦测副本,被侦测的副本将无法触达,Sentinel将永远不可能为master实施故障转移,因在系统的视角来看没有健康可用的副本,所以当前没有办法用Sentinel监控一组部署在Docker里的Master和副本,除非你命令Docker以1:1的方式来映射。
因第一个问题,万一你要使用带有转发端口(或者其他一些端口被重映射的NAT设置)的Docker来运行一组Sentinel实例,你可以使用如下两个Sentinel配置指令来强制Sentinel声明一个特有的ip和端口:
sentinel announce-ip <ip>
sentinel announce-port <port>
注意Docker拥有运行在host网络模式(查看--net=host 选项以获取更多信息)下的能力,这不会产生问题,因为在这个设置下,端口不会重映射。
IP和DNS
老一点的Sentinel版本不支持主机名,并要求所有地方使用ip地址。从6.2开始,Sentinel对主机名提供可选支持。
这个能力默认被禁用。如果你打算启用DNS/hostnames,请注意:
- Redis和Sentinel节点上的名称解析配置必须可靠,并能够快速解析地址。地址解析的意外延迟可能会对Sentinel产生负面影响。
- 你需要在每个地方都使用主机名,避免混合使用主机名和ip地址。为此,你需要对所有Redis和Sentinel实例分别运行 replica-announce-ip <hostname> 和 sentinel announce-ip <hostname> 命令。
启用resolve-hostnames 全局配置允许Sentinel接受主机名:
- 作为sentinel monitor命令的一部分
- 作为副本地址,如果在replica-announce-ip中副本使用一个主机名
Sentinel将接受主机名为合法输入且解析它,但仍然会在声明一个实例、更新配置文件等场景中使用ip地址。
通过启用announce-hostnames全局配置让Sentinel使用主机名,这个会影响对客户端的回复、写入配置文件的值、发布到副本的REPLICAOF命令,等。
这个行为可能无法兼容那些明确需要ip地址的Sentinel客户端。
当客户端使用TLS来连接实例且需要一个名字而不是ip地址来执行ASN匹配时,使用主机名可能很有用。