redis哨兵

最后更新:2020-04-07

Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。

Redis-Sentinel是一个分布式架构,其中包含若干个sentinel节点和redis数据节点,每个sentinel节点会对数据节点和其余sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他sentinel节点进行协商,当大多数sentinel节点都认为主节点不可达时,它们会选举出一个sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给redis应用方。

Sentinel的架构

Redis Sentinel主要作用

  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个Redis服务器出现问题时, Sentinel可以通过API向管理员或者其他应用程序发送通知。
  • 配置提供者(Configurationprovider):客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。
  • 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。

其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移;而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

1. 部署

物理结构

  • master 6379
  • slave-1 6380
  • slave-2 6381
  • sentinel-1 26379
  • sentinel-2 26380
  • sentinel-3 26381

主从的部署不做描述,下面着重看sentinel的部署,哨兵节点本质上是特殊的 Redis 节点。3 个哨兵节点的配置几乎是完全一样的,主要区别在于端口号的不同

配置

sentinel的配置文件sentinel.conf

#sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2

sentinel monitor mymaster 127.0.0.1 6379 2代表sentinel节点需要监控127.0.0.1:6379这个主节点,2代表主节点失败至少需要2个sentinel节点统一,mymaster是主节点的别名

启动sentinel节点

1.使用redis-sentinel命令

./src/redis-sentinel sentinel.conf

2.使用redis-server命令加–sentinal参数

./redis-server /etc/sentinel.conf --sentinel

按照上述方式配置和启动之后,整个哨兵系统就启动完毕了,可以通过 redis-cli 连接哨兵节点进行验证。

确认sentinel状态

./src/redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

每个sentinel节点的myid要唯一

sentinel myid 2fb1c95bdb67993090dd367c6b1c7bd6e8480c55

启动后重新打开sentinel.conf,发送redis自动添加了一下配置参数

# Generated by CONFIG REWRITE
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-slave mymaster 127.0.0.1 6381
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 26380 6eada51eed6fbc1492859df0f3cc442c113d6f6d
sentinel known-sentinel mymaster 127.0.0.1 26381 214bb3ba6e408ecc43944ddde9e522fd55f32818
sentinel current-epoch 0

known-slave 和 known-sentinel 显示哨兵已经发现了从节点和其他哨兵。

带有 epoch 的参数与配置纪元有关(配置纪元是一个从 0 开始的计数器,每进行一次领导者哨兵选举,都会 +1;领导者哨兵选举是故障转移阶段的一个操作。

2. 演示故障转移

下面演示当主节点发生故障时,哨兵的监控和自动故障转移功能

使用 Kill 命令杀掉主节点后,立即查看sentinel状态,会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移,需要一段时间。

./src/redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

一段时间以后,再次在哨兵节点中执行 info Sentinel 查看,发现主节点已经切换成 6381 节点。

./src/redis-cli -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3

但是同时可以发现,哨兵节点认为新的主节点仍然有 2 个从节点,这是因为哨兵在将 6381 切换成主节点的同时,将 6379 节点置为其从节点。

虽然 6379 从节点已经挂掉,但是由于哨兵并不会对从节点进行客观下线,因此认为该从节点一直存在。

当 6379 节点重新启动后,会自动变成 6380 节点的从节点

./src/redis-cli -p 6381 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=102745,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=102745,lag=1
master_replid:b787d6ba42fe28b09e1ed437891f18d99098bed6
master_replid2:8e1b2b3917d60dd60ff88bedd0dae0475eab9fe3
master_repl_offset:102745
second_repl_offset:63451
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:102731

在故障转移阶段,哨兵和主从节点的配置文件都会被改写。

主从节点配置

# 6379,6380
slaveof 127.0.0.1 6381

哨兵配置

# Generated by CONFIG REWRITE
sentinel monitor mymaster 127.0.0.1 6381 2
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel known-slave mymaster 127.0.0.1 6379
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 26380 6eada51eed6fbc1492859df0f3cc442c113d6f6d
sentinel known-sentinel mymaster 127.0.0.1 26381 214bb3ba6e408ecc43944ddde9e522fd55f32818
sentinel current-epoch 1

可以看到纪元相关的参数都 +1 了。

本章的例子中,一个哨兵只监控了一个主节点;实际上,一个哨兵可以监控多个主节点,通过配置多条 sentinel monitor 即可实现。

3. 客户端访问哨兵系统

进入客户端redis-cli -p 26379

sentinel masters

展示所有被监控的主节点状态以及相关统计信息

sentinel master <master name>

展示指定的主节点的状态以及相关统计信息

sentinel slaves <master name>

展示指定的从节点的状态以及相关统计信息

sentinel sentinels <master name>

展示指定的sentinel节点集合(不包含当前sentinel节点)

sentinel get-master-addr-by-name <master name>

返回指定主节点的ip和端口

127.0.0.1:26379> sentinel get-master-addr-by-name mymaster
1) "192.168.225.128"
2) "6379"

当前sentinel对符合pattern(通配符风格)主节点进行重置,包含清除主节点的相关状态(例如故障转移),重新发现重节点和sentinel节点

 sentinel reset <pattern>

sentinel reset mymaster sentinel-1节点对mymaster节点重置状态

对指定主节点进行强制故障转移(没有和其他sentinel节点协商)故障转移完成后,其他的sentinel节点按照故障转移的结果更新自身配置(该命令在日常运维中非常有用)

sentinel failover <master name>

示例

127.0.0.1:26379> sentinel failover mymaster
OK
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3

检测当前可达的sentinel节点总数是否达到quorum的个数

sentinel ckquorum <master name>

取消当前sentinel节点对于指定master name主节点的监控

sentinel remove <master name>

和配置文件中该行命令的作用相同

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

如:sentinel monitor mymaster-1 192.168.225.128 6379 2

客户端可以通过命令获得当前主节点

127.0.0.1:26379> sentinel get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6381"

然后增加对哨兵的监听:这样当发生故障转移时,客户端便可以收到哨兵的通知,从而完成主节点的切换。

4. 通知

通过接收 Sentinel 发送的通知: 当执行故障转移操作, 或者某个被监视的实例被判断为主观下线或者客观下线时, Sentinel 就会发送相应的信息。

一个频道能够接收和这个频道的名字相同的事件。 比如说, 名为 +sdown 的频道就可以接收所有实例进入主观下线(SDOWN)状态的事件。

通过执行 PSUBSCRIBE * 命令可以接收所有事件信息。

进行一次主动的failover,各个频道的输出中涉及了新纪元(epoch)通知、投票、选举、新主和新slave的通告、角色转变通知,最终完成了这次主动failover过程。

例如:

127.0.0.1:26380> psubscribe *
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "*"
3) (integer) 1
1) "pmessage"
2) "*"
3) "+sdown"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+odown"
4) "master mymaster 127.0.0.1 6380 #quorum 2/2"
1) "pmessage"
2) "*"
3) "+new-epoch"
4) "3"
1) "pmessage"
2) "*"
3) "+try-failover"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+vote-for-leader"
4) "6eada51eed6fbc1492859df0f3cc442c113d6f6d 3"
1) "pmessage"
2) "*"
3) "+elected-leader"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+failover-state-select-slave"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+selected-slave"
4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+failover-state-send-slaveof-noone"
4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+failover-state-wait-promotion"
4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "-role-change"
4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380 new reported role is master"
1) "pmessage"
2) "*"
3) "+promoted-slave"
4) "slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+failover-state-reconf-slaves"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+slave-reconf-sent"
4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "-odown"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+slave-reconf-inprog"
4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+slave-reconf-done"
4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+failover-end"
4) "master mymaster 127.0.0.1 6380"
1) "pmessage"
2) "*"
3) "+switch-master"
4) "mymaster 127.0.0.1 6380 127.0.0.1 6381"
1) "pmessage"
2) "*"
3) "+slave"
4) "slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381"
1) "pmessage"
2) "*"
3) "+slave"
4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381"
1) "pmessage"
2) "*"
3) "-role-change"
4) "slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381 new reported role is master"

以下列出的是客户端可以通过订阅来获得的频道和信息的格式:第一个英文单词是频道/事件的名字,其余的是数据的格式。

注意,当格式中包含 instance details 字样时,表示频道所返回的信息中包含了以下用于识别目标实例的内容:

<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>

@ 字符之后的内容用于指定主服务器,这些内容是可选的,它们仅在 @ 字符之前的内容指定的实例不是主服务器时使用。

  • +reset-master <instance details> :主服务器已被重置。
  • +slave <instance details> :一个新的从服务器已经被 Sentinel 识别并关联。
  • +failover-state-reconf-slaves <instance details> :故障转移状态切换到了 reconf-slaves 状态。
  • +failover-detected <instance details> :另一个 Sentinel 开始了一次故障转移操作,或者一个从服务器转换成了主服务器。
  • +slave-reconf-sent <instance details> :领头(leader)的 Sentinel 向实例发送了 SLAVEOF命令,为实例设置新的主服务器。
  • +slave-reconf-inprog <instance details> :实例正在将自己设置为指定主服务器的从服务器,但相应的同步过程仍未完成。
  • +slave-reconf-done <instance details> :从服务器已经成功完成对新主服务器的同步。
  • -dup-sentinel <instance details> :对给定主服务器进行监视的一个或多个 Sentinel 已经因为重复出现而被移除 —— 当 Sentinel 实例重启的时候,就会出现这种情况。
  • +sentinel <instance details> :一个监视给定主服务器的新 Sentinel 已经被识别并添加。
  • +sdown <instance details> :给定的实例现在处于主观下线状态。
  • -sdown <instance details> :给定的实例已经不再处于主观下线状态。
  • +odown <instance details> :给定的实例现在处于客观下线状态。
  • -odown <instance details> :给定的实例已经不再处于客观下线状态。
  • +new-epoch <instance details> :当前的纪元(epoch)已经被更新。
  • +try-failover <instance details> :一个新的故障迁移操作正在执行中,等待被大多数 Sentinel 选中(waiting to be elected by the majority)。
  • +elected-leader <instance details> :赢得指定纪元的选举,可以进行故障迁移操作了。
  • +failover-state-select-slave <instance details> :故障转移操作现在处于 select-slave 状态 —— Sentinel 正在寻找可以升级为主服务器的从服务器。
  • no-good-slave <instance details> :Sentinel 操作未能找到适合进行升级的从服务器。Sentinel 会在一段时间之后再次尝试寻找合适的从服务器来进行升级,又或者直接放弃执行故障转移操作。
  • selected-slave <instance details> :Sentinel 顺利找到适合进行升级的从服务器。
  • failover-state-send-slaveof-noone <instance details> :Sentinel 正在将指定的从服务器升级为主服务器,等待升级功能完成。
  • failover-end-for-timeout <instance details> :故障转移因为超时而中止,不过最终所有从服务器都会开始复制新的主服务器(slaves will eventually be configured to replicate with the new master anyway)。
  • failover-end <instance details> :故障转移操作顺利完成。所有从服务器都开始复制新的主服务器了。
  • +switch-master <master name> <oldip> <oldport> <newip> <newport> :配置变更,主服务器的 IP 和地址已经改变。 这是绝大多数外部用户都关心的信息。
  • +tilt :进入 tilt 模式。
  • -tilt :退出 tilt 模式。

一次故障转移操作由以下步骤组成:

  1. 由sentinel主动发起failover或者发现主服务器已经进入客观下线状态。
  2. sentinel对我们的当前纪元(epoch)进行自增,并尝试在这个纪元中当选为此次failover的总指挥。
  3. 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。
  4. 选出一个从redis实例,并将它升级为主redis实例
  5. 向被选中的从redis实例发送 SLAVEOF NO ONE 命令,让它转变为主redis实例
  6. 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
  7. 向已下线主服务器的从服务器发送SLAVEOF命令, 让它们去复制新的主服务器
  8. 当所有从redis实例都已经开始复制新的主redis实例时, 领头Sentinel 终止这次故障迁移操作。

5. 原理

5.1. 定时任务

每个哨兵节点维护了 3 个定时任务,定时任务的功能分别如下:

  • 通过向主从节点发送 info 命令获取最新的主从结构。
  • 通过发布订阅功能获取其他哨兵节点的信息。
  • 通过向其他节点发送 ping 命令进行心跳检测,判断是否下线。

5.2. 主观下线和客观下线

sentinel对于不可用有两种不同的看法,一个叫主观下线(SDOWN),另外一个叫客观下线(ODOWN)。

主观下线

在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。

顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。

客观下线

哨兵节点在对主节点进行主观下线后,会通过 sentinelis-master-down-by-addr 命令询问其他哨兵节点该主节点的状态。

如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。

需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。

从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。这个时间在配置中通过is-master-down-after-milliseconds参数配置。

当sentinel发送PING后,以下回复之一都被认为是合法的:

PING replied with +PONG.
PING replied with -LOADING error.
PING replied with -MASTERDOWN error.

其它任何回复(或者根本没有回复)都是不合法的。

从SDOWN切换到ODOWN不需要任何一致性算法,只需要一个gossip协议:如果一个sentinel收到了足够多的sentinel发来消息告诉它某个master已经down掉了,SDOWN状态就会变成ODOWN状态。如果之后master可用了,这个状态就会相应地被清理掉。

正如之前已经解释过了,真正进行failover需要一个授权的过程,但是所有的failover都开始于一个ODOWN状态。ODOWN状态只适用于master,对于不是master的redis节点sentinel之间不需要任何协商,slaves和sentinel不会有ODOWN状态。

选举领导者哨兵节点

当主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。

监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是 Raft 算法。

Raft 算法的基本思路是先到先得:即在一轮选举中,哨兵 A 向 B 发送成为领导者的申请,如果 B 没有同意过其他哨兵,则会同意 A 成为领导者。

5.3. 故障转移

选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为 3 个步骤:

  • 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点,然后选择优先级最高的从节点(由 slave-priority 指定)。

    如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择 runid 最小的从节点。

  • 更新主从状态:通过 slaveof no one 命令,让选出来的从节点成为主节点;并通过 slaveof 命令让其他节点成为其从节点。

  • 将已经下线的主节点(即 6379)设置为新的主节点的从节点,当 6379 重新上线后,它会成为新的主节点的从节点。

6. 配置

**sentinel monitor **

sentinel monitor 是哨兵最核心的配置,在前文讲述部署哨兵节点时已说明,其中:masterName 指定了主节点名称,masterIp 和 masterPort 指定了主节点地址,quorum 是判断主节点客观下线的哨兵数量阈值。

当判定主节点下线的哨兵数量达到 quorum 时,对主节点进行客观下线。建议取值为哨兵数量的一半加 1。

sentinel monitor mymaster 127.0.0.1 6379 2
  • <ip> <redis-port>当前Sentinel节点监控 127.0.0.1:6379 这个主节点
  • <quorum>2代表判断主节点失败至少需要2个Sentinel节点节点同意,一般建议将其设置为sentinel节点的一半加1
  • <master-name> mymaster是主节点的别名

quorum还和sentinel节点的领导者选举有关,至少要有max(quorum, num(sentinel)/2 + 1)个sentinel节点参与选举才能选出领导者sentinel

**sentinel down-after-milliseconds **

sentinel down-after-milliseconds 与主观下线的判断有关:哨兵使用 ping 命令对其他节点进行心跳检测。如果其他节点超过 down-after-milliseconds 配置的时间没有回复,或者返回一个错误,哨兵就会将其进行主观下线,该配置对主节点、从节点和哨兵节点的主观下线判定都有效。

不过只有一个Sentinel将服务器标记为主观下线并不一定会引起服务器的自动故障迁移,只有在足够数量的Sentinel都将一个服务器标记为主观下线之后,服务器才会被标记为客观下线(objectively down,简称ODOWN), 这时自动故障迁移才会执行。将服务器标记为客观下线所需的Sentinel数量由对主服务器的配置(sentinel monitor参数)决定。

down-after-milliseconds的默认值是 30000,即 30s;可以根据不同的网络环境和应用要求来调整。值越大,代表sentinel节点对节点不可达的条件越宽松,反之就越严格。

条件宽松意味着应用方故障时间可能越长,好处是误判的可能性小。条件严格虽然可以及时发现故障完成故障转移,但是也存在一定的误判率

例如,如果应用对可用性要求较高,则可以将值适当调小,当故障发生时尽快完成转移;如果网络环境相对较差,可以适当提高该阈值,避免频繁误判。

**sentinel parallel-syncs **

parallel-syncs与故障转移之后从节点的复制有关:它规定了每次向新的主节点发起复制操作的从节点个数。

例如,假设主节点切换完成之后,有 3 个从节点要向新的主节点发起复制;如果 parallel-syncs=1,则从节点会一个一个开始复制;如果 parallel-syncs=3,则 3 个从节点会一起开始复制。

parallel-syncs 取值越大,从节点完成复制的时间越快,但是对主节点的网络负载、硬盘负载造成的压力也越大;应根据实际情况设置。

例如,如果主节点的负载较低,而从节点对服务可用的要求较高,可以适量增加 parallel-syncs 取值。parallel-syncs 的默认值是 1。

如果从服务器被设置为允许使用过期数据集(slave-serve-stale-data选项), 那么你可能不希望所有从服务器都在同一时间向新的主服务器发送同步请求。因为尽管复制过程的绝大部分步骤都不会阻塞从服务器,但从服务器在载入主服务器发来的RDB文件时,仍然会造成从服务器在一段时间内不能处理命令请求。

如果全部从服务器一起对新的主服务器进行同步,那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。你可以通过将这个值设为1来保证每次只有一个从服务器处于不能处理命令请求的状态。

**sentinel failover-timeout **

failover-timeout 与故障转移超时的判断有关,但是该参数不是用来判断整个故障转移阶段的超时,而是其几个子阶段的超时。

failover-timeout作用于故障转移的各个阶段:

  • a) 选出合适从节点
  • b) 晋升选出的从节点为主节点
  • c) 命令其余从节点复制新的主节点
  • d) 等待原主节点恢复后命令它去复制新的主节点

failover-timeout 的默认值是 180000,即 180s;如果sentinel对一个主节点故障转移失败,那么下次再对该主节点做故障转移的起始时间是failover-timeout的2倍。

在b)阶段,如果sentinel节点向a)阶段选出来的从节点执行slaveof no one一直失败,当此过程超过failover-timeout时,认为故障转移失败

在b阶段如果执行成功,sentinel还会继续执行info命令来确认a)阶段选出来的节点确实晋升为主节点,如果此过程执行时间超过failover-timeout时,则故障转移失败

如果c)节点执行时间超过了failover-timeout(不包含复制时间),则故障转移失败。但是即使超过了这个时间,sentinel节点也会最终配置从节点去同步最新的主节点

如果在多少毫秒内没有把宕掉的那台Master恢复,那Sentinel认为这是一次真正的宕机。在下一次选取时排除该宕掉的Master作为可用的节点,然后等待一定的设定值的毫秒数后再来探测该节点是否恢复,如果恢复就把它作为一台Slave加入Sentinel监测节点群,并在下一次切换时为他分配一个”选取号”。

sentinel auth-pass mymaster MySUPER–secret-0123passw0rd

当Master设置了密码时,Sentinel连接Master和Slave时需要通过设置参数auth-pass配置相应密码。

**sentinel notification-script **

指定Sentinel检测到该监控的Redis实例failover时调用的报警脚本。脚本被允许执行的最大时间为60秒,超过这个时间脚本会被kill。该配置项可选,但线上系统建议配置。这里的通知脚本简单的记录一下failover事件。

# 创建通知脚本
$ vim /etc/redis/notify.sh

#! /bin/bash
#获取所有参数
msg=$*
#报警脚本
echo "master failovered at `date`" > /var/log/redis/redis_issues.log
exit 0

# 给脚本增加执行权限
$ chmod +x /etc/redis/notify.sh

如果脚本以exit 1结束,脚本稍后重试执行,如果以exit 2或更高值结束,脚本不会重试,正常返回值是exit 0

**sentinel client-reconfig-script **

在故障转移结束后执行的脚本,该配置项可选,但线上系统建议配置。

sentinel也支持监控多个主节点,只需要知道多个mastername来区分不同的主节点即可

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

sentinel也只支持通过sentinel set <param> <value>动态设置参数,它仅对当前sentinel节点有效,而且执行成功后会立即刷新配置文件

7. 实践建议

哨兵节点的数量应不止一个,一方面增加哨兵节点的冗余,避免哨兵本身成为高可用的瓶颈;另一方面减少对下线的误判。此外,这些不同的哨兵节点应部署在不同的物理机上。

哨兵节点的数量应该是奇数,便于哨兵通过投票做出“决策”:领导者选举的决策、客观下线的决策等。

各个哨兵节点的配置应一致,包括硬件、参数等;此外,所有节点都应该使用 ntp 或类似服务,保证时间准确、一致。

哨兵的配置提供者和通知客户端功能,需要客户端的支持才能实现;如果开发者使用的库未提供相应支持,则可能需要开发者自己实现。

8.为什么需要3台哨兵才能实现高可用

首先我们要定义一下对于Redis服务来说怎样才算是高可用,即在各种出现异常的情况下,依然可以正常提供服务。或者宽松一些,出现异常的情况下,只经过很短暂的时间即可恢复正常服务。所谓异常,应该至少包含了以下几种可能性:

  • 【异常1】某个节点服务器的某个进程突然down掉(例如某开发手残,把一台服务器的Redis-Server进程kill了)。
  • 【异常2】某台节点服务器down掉,相当于这个节点上所有进程都停了(例如某运维手残,把一个服务器的电源拔了;例如一些老旧机器出现硬件故障)。
  • 【异常3】任意两个节点服务器之间的通信中断了(例如某临时工手残,把用于两个机房通信的光缆挖断了)。

其实以上任意一种异常都是小概率事件。

而做到高可用性的基本指导思想就是:多个小概率事件同时发生的概率可以忽略不计。只要我们设计的系统可以容忍短时间内的单点故障,即可实现高可用性。

假设我们只部署两个Sentinel进程同时为客户端提供服务发现的功能。对于客户端来说,它可以连接任何一个Redis Sentinel服务,来获取当前Redis Server实例的基本信息。

通常情况下,我们会在Client端配置多个Redis Sentinel的链接地址,Client一旦发现某个地址连接不上,会去试图连接其他的Sentinel实例。

我们预期是:即使其中一个Redis Sentinel挂掉了,还有另外一个Sentinel可以提供服务。然而,愿景是美好的,现实却是很残酷的。如此架构下,依然无法实现Redis服务的高可用。

上面方案的示意图,红线部分是两台服务器之间的通信,而我们所设想的异常场景(【异常2】)是,某台服务器整体down机,不妨假设服务器1停机,此时,只剩下服务器2上面的Redis Sentinel和slave Redis Server进程。

这时,Sentinel其实是不会将仅剩的slave切换成master继续服务的,也就导致Redis服务不可用,因为Redis的设定是只有当超过50%的Sentinel进程可以连通并投票选取新的master时,才会真正发生主从切换。本例中两个Sentinel只有一个可以连通,等于50%并不在可以主从切换的场景中。

为什么Redis要有这个50%的设定?

假设我们允许小于等于50%的Sentinel连通的场景下也可以进行主从切换。试想一下【异常3】,即服务器1和服务器2之间的网络中断,但是服务器本身是可以运行的。如下图所示:

实际上对于服务器2来说,服务器1直接down掉和服务器1网络连不通是一样的效果,反正都是突然就无法进行任何通信了。

假设网络中断时我们允许服务器2的Sentinel把slave切换为master,结果就是你现在拥有了两个可以对外提供服务的Redis Server。

Client做任何的增删改操作,有可能落在服务器1的Redis上,也有可能落在服务器2的Redis上(取决于Client到底连通的是哪个Sentinel),造成数据混乱。即使后面服务器1和服务器2之间的网络又恢复了,那我们也无法把数据统一了(两份不一样的数据,到底该信任谁呢?),数据一致性完全被破坏。

主从同步Redis Server,三实例Sentinel

我们引入了服务器3,并且在3上面又搭建起一个RedisSentinel进程,现在由三个Sentinel进程来管理两个Redis Server实例。这种场景下,不管是单一进程故障、还是单个机器故障、还是某两个机器网络通信故障,都可以继续对外提供Redis服务。

如果你的机器比较空闲,当然也可以把服务器3上面也开启一个Redis Server,形成1master+2slave的架构,每个数据都有两个备份,可用性会提升一些。当然也并不是slave越多越好,毕竟主从同步也是需要时间成本的。

在这个方案中,一旦服务器1和其他服务器的通信完全中断,那么服务器2和3会将slave切换为master。对于客户端来说,在这么一瞬间会有2个master提供服务,并且一旦网络恢复了,那么所有在中断期间落在服务器1上的新数据都会丢失。

如果想要部分解决这个问题,可以配置Redis Server进程,让其在检测到自己网络有问题的时候,立即停止服务,避免在网络故障期间还有新数据进来(可以参考Redis的min-slaves-to-write和min-slaves-max-lag这两个配置项)。

至此,我们就用3台机器搭建了一个高可用的Redis服务

9. 参考资料

https://mp.weixin.qq.com/s/9QkGfjtQNfe9lQqbpDXwLg

https://mp.weixin.qq.com/s/-LAD7y9Mk5GC0mamY2MfcQ

Edgar

Edgar
一个略懂Java的小菜比