【Redis 高可用】Sentinel
哨兵(Redis Sentinel)
简介
Redis Sentinel provides high availability for Redis when not using Redis Cluster.
当没有使用 Redis Cluster 时,Redis 哨兵(Sentinel)为 Redis 提供了高可用的能力。Redis Sentinel 还提供其他附带任务,例如监控、通知并充当客户端的配置提供程序。
Sentinel 具备的功能如下:
-
监控(Monitoring):Sentinel 会不断检查 Master 和 Replica 实例是否按预期工作;
-
通知(Notification):如果一个被监控的 Redis 实例出现问题,Sentinel 会将故障转移的结果通知给客户端;
-
自动故障转移(Automatic failover):如果 master 没有按预期工作,Sentinel 可以启动一个故障转移过程,其中一个副本被提升为 master,其他剩余的副本会被配置为使用这个新的 master,并且,使用这个 Redis 服务器的应用程序被告知需要使用的新地址当它与 Redis 建立连接时。
-
配置中心(Configuration provider):sentinel 架构中,客户端在初始化的时候连接的是 sentinel 集群,从中获取主节点信息。如果发生故障转移,Sentinels 将报告新地址。
此外,使用 sentinel 集群而不是单个 sentinel 节点去监控 redis 主从架构有两个好处:
-
对于节点的故障判断由多个 sentinel 节点共同完成,这样可以有效地防止误判;
-
sentinel 集群可以保证自身的高可用性,即某个 sentinel 节点自身故障也不会影响 sentinel 集群的健壮性。
启动
哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。可以通过如下方式启动一个哨兵实例:
redis-server /path/to/sentinel.conf --sentinel
Sentinels 启动后默认监听 TCP 端口 26379 。
Sentinel 部署的注意事项:
-
至少需要三个 Sentinel 实例才能进行可靠的部署;
-
三个 Sentinel 实例最好是独立地部署在不同的可用区;
-
Sentinel + Redis 的分布式系统不能保证故障期间保存已确认的写入,因为,Redis 使用的是异步复制;
-
客户端需要支持 Sentinel;
哨兵配置
一个典型的 sentinel.conf 最小配置文件如下所示:
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 <option_name> <master_name> <option_value>
其中,常用的选项如下:
监控
sentinel monitor 这个命令用于开始监控一个新的 Master 实例。
其语法含义如下:
sentinel monitor <master-name> <ip> <port> <quorum>
其中,它的参数含义如下:
<master-name>
:指定需要监控的 master 名称,每一个 master 需要指定一个唯一的名称。
前面的示例配置监控了两组 Redis 实例,每个实例由一个 master 实例和未定义数量的副本组成,一组实例名称为 mymaster,另一组实例名称为 resque。
注意,我们配置哨兵时,只需要指定需要监控的 master 即可,不需要指定副本,副本会被哨兵自动发现,并自动更新到配置信息中。每次故障转移期间,副本被提升为主服务器、每当发现有新的哨兵时,都会重写到配置中。
-
<ip>
:master 实例监听的IP; -
<port>
:master 实例监听的端口号; -
<quorum>
:就 master 不可达这一事实达成一致的哨兵数量,以便真正将 master 标记为失败,并在可能的情况下最终启动故障转移流程。注意,仲裁仅用于检测故障,为了实际执行故障转移,其中一个哨兵需要被选为故障转移的 Leader 并被授权继续进行。并且,这过程只会发生在大多数 Sentinel 进程的投票中。
响应超时时间:down-after-milliseconds
sentinel down-after-milliseconds 表示Redis 实例响应超时时间吗,单位是毫秒。
哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。如果,如果 Redis实例超过 sentinel down-after-milliseconds
时间未响应,就会被认为主观下线。
parallel-syncs
parallel-syncs:在故障转移后可以重新配置为同时使用新主服务器的副本数量。
哨兵的常用操作命令
查看 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"
...
35) "quorum"
36) "2"
37) "failover-timeout"
38) "60000"
39) "parallel-syncs"
40) "1"
其中,flag
字段表示 master 的状态,它的值可能是 master、s_down、o_down 的其中一个。
获取当前 master 的地址
Sentinel 节点可以通过如下命令查询故障转移后的新的 master 节点的地址,以通知给客户端程序:
127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster
1) "127.0.0.1"
2) "6379"
其他常用命令
通过哨兵查询指定 master 的副本:
SENTINEL replicas mymaster
通过哨兵查询指定 master 的其他哨兵节点信息:
SENTINEL sentinels mymaster
添加和删除哨兵
添加哨兵
由于 Sentinel 的自动发现机制,将新的 Sentinel 只需要按照哨兵的启动方法,将其配置为监视当前活动主节点即可。
删除哨兵
为了删除 Sentinel,应在没有网络分区的情况下执行以下步骤:
-
停止该哨兵的 Sentinel 进程。
-
SENTINEL RESET *
:向所有其他 Sentinel 实例发送命令(*如果您只想重置一个主机,则可以使用确切的主机名称)。一个接一个,实例之间至少等待 30 秒。 -
通过检查每个哨兵的输出,检查所有哨兵是否同意当前活跃的哨兵数量SENTINEL MASTER mastername。
哨兵和副本自动发现
Sentinel 可以与其他 Sentinel 保持连接,以相互检查彼此的可用性并交换消息。我们不需要在运行的每个 Sentinel 实例中配置其他 Sentinel 地址的列表,因为,Sentinel 使用了 Redis 实例的发布/订阅功能,Sentinel 可以发现监控相同主节点实例及其副本实例的所有其他 Sentinel。
Redis 节点的自动发现
sentinel 集群通过三个定时监控任务完成对各个节点发现和监控。
每隔10秒,每个 sentinel 节点会向主节点发送 INFO
命令获取 redis 主从架构的最新情况。例如,发送 info replication
命令可以得到以下信息:
node01:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.239.102,port=6379,state=online,offset=18621889,lag=1
slave1:ip=192.168.239.103,port=6379,state=online,offset=18621889,lag=1
这样,sentinel 集群就可以得知 master 和 slave 的基本信息,通过向主节点执行 INFO
命令,获取从节点的信息,所以 sentinel 节点不需要显式配置监控从节点,当有新的从节点加入时,就可以立刻被感知出来,当 master 节点故障或者故障转移后,可以通过 INFO
命令实时更新 redis 主从信息。
Sentinel 节点的自动发现
每个 Sentinel 节点每隔 2 秒会向主节点和所有从节点的 __sentinel__:hello
频道(channel)发送一条 Hello 消息,其中,Hello 消息的内容格式如下:
<sentinel-ip> <sentinel-port> <sentinel-runId> <sentinel 配置版本号> <master-name> <master-ip> <master-port> <master 配置版本号>
每个 Sentinel 节点都会订阅主节点和所有从节点的__sentinel__:hello
频道(channel),来查找其他的 Sentinel 节点、以及它们对主节点的判断,作用如下:
-
发现新的 Sentinel 节点
通过订阅主节点的
__sentinel__:hello
频道(channel)了解其他的 Sentinel 节点信息,如果检测到新加入的 Sentinel 节点,就将该 Sentinel 节点信息保存起来,并与该 Sentinel 节点创建连接。注意,在向主服务器添加新哨兵之前,哨兵会检查是否已经存在具有相同 runid 或相同地址(ip:port)的哨兵。
-
Sentinel 节点之间交换主节点的状态,用于确认 master 下线和负责执行故障转移的哨兵 leader 选举。
Hello 消息包含了 master 的完整当前配置,如果接收到消息的 Sentinel 节点已保存的 master 的配置比收到的要旧,它会立即将其更新为新的配置。
每隔 1 秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条 PING
命令做一次心跳检测,来确认这些节点是否可达。
通过定时发送 PING
命令,Sentinel 节点对主节点、从节点、其余 Sentinel 节点都建立起连接,实现了对每个节点的监控。
故障状态
Redis Sentinel 有两种故障状态:主观下线(Subjectively Down condition) 和 客观下线(Objectively Down condition)。
主观下线(SDown)
哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行,如果,如果 Redis 实例超过 sentinel down-after-milliseconds
配置的时间未响应,就会被认为主观下线。
PING命令的响应,只能是以下其中之一:
-
响应
+PONG
; -
响应
-LOADING
错误; -
响应
-MASTERDOWN
错误;
如果响应其他内容,或者不响应,就认为响应无效。另外,如果一个主节点的INFO输出表明它是一个 Replica ,那么,该主节点会被认为下线了。
注意,主观下线是某个 Sentinel 节点的判断,并不是 Sentinel 集群的判断,所以存在误判的可能,因此,要触发故障转移,必须达到 ODOWN 状态。
客观下线(ODown)
当 Sentinel 主观下线的节点是主节点时,该 Sentinel 节点会通过 sentinel ismaster-down-by-addr
命令向其他 Sentinel 节点询问对主节点的判断,当超过 <quorum>
个数的 Sentinel 节点认为主节点确实有问题,这时该 Sentinel 节点会做出客观下线的决定,这样客观下线的含义是比较明显了,也就是大部分是 Sentinel 节点都对主节点的下线做了同意的判定,那么这个判定就是客观的。
ODOWN 条件仅适用于主节点。对于其他类型的实例,哨兵不需要采取行动,因此副本和其他哨兵永远不会达到 ODOWN 状态。
故障转移
仲裁(Quorum)
仲裁(quorum):当检测到错误条件时,将 master 标记为 ODOWN 状态的哨兵进程数。它指定了需要就 master 的不可达或错误条件达成一致,以触发故障转移的 Sentinel 进程的数量。
在触发故障转移之后,为了让故障转移真正进行,必须至少有过半数的 Sentinel 节点授权该哨兵(Leader)节点进行故障转移。
一旦故障转移被触发,试图进行故障转移的哨兵节点需要向大多数哨兵节点请求授权(或者超过多数,如果法定人数设置为大于多数的数字)。
例如,如果有 5 个 Sentinel 节点,并且 Quorum 设置为 2,一旦 2 个 Sentinels 认为 master 不可达,就会触发故障转移。但是,这两个哨兵中的任意一个哨兵节点,如果收到了至少 3 个哨兵的授权,那么就也可以执行故障转移。
相反,如果 quorum 配置为 5,则只有当所有哨兵节点就主节点错误条件达成一致,并且,得到所有 Sentinel 节点的授权才能进行故障转移。
这意味着可以通过两种方式使用仲裁(quorum)来调整 Sentinel:
-
如果 quorum 的值小于大多数哨兵,会让哨兵对 master 的故障更加敏感,即使只有少数哨兵不再能够与 master 通信,也会立即触发故障转移。
-
如果 quorum 的值大于大多数哨兵,我们将使哨兵仅在有大量(大于多数)连接良好的哨兵同意 master 已关闭时,才能进行故障转移。
配置版本号(Configuration Epoch)
When a Sentinel is authorized, it gets a unique configuration epoch for the master it is failing over. This is a number that will be used to version the new configuration after the failover is completed. Because a majority agreed that a given version was assigned to a given Sentinel, no other Sentinel will be able to use it. This means that every configuration of every failover is versioned with a unique version. We'll see why this is so important.
当一个哨兵(Leader)节点被授权进行故障转移时,它会为 master 分配一个唯一的配置版本号(configuration epoch),在故障转移结束后,这个版本号会用于对配置(configuration)进行版本控制。
即每次故障转移的配置都会使用唯一版本号进行版本控制。
在选举 leader 过程中,如果本次选举失败,进行下一次选举时,就会分配一个新的配置版本号,也就是说,每次 leader 选举都对应一个新的配置版本号,在故障转移的过程中,也要求各个 sentinel 节点使用相同的配置版本号。
This means that Sentinels will not try to failover the same master at the same time, the first to ask to be authorized will try, if it fails another will try after some time, and so forth.
如果 leader 对 master 进行故障转移失败,那么参与本次投票的某一个哨兵节点,会等待一段时间( 2 * failover-timeout
)后,再次尝试对这个 master 进行故障转移,其他剩余哨兵节点也是如此。
注意:
-
不会有多个哨兵节点同时对同一个 master 进行故障转移;
-
参与投票的哨兵的等待时间,可以通过 sentinel.conf 配置文件中
failover-timeout
配置。
在故障转移成功之后,sentinel leader 会将 master 的配置更新为最新,其配置版本号也会更新,然后,再同步给其他的 sentinel 节点,这样就保证了 sentinel 集群中保存的主从节点配置都是最新的,这样,当 client 请求的时候就会拿到最新的配置信息。
Redis Sentinel guarantees the liveness property that if a majority of Sentinels are able to talk, eventually one will be authorized to failover if the master is down.
Redis Sentinel also guarantees the safety property that every Sentinel will failover the same master using a different configuration epoch.
如果大多数哨兵能与 master 建立连接,那么哨兵就保证它是存活的,最终,只有一个被授权的哨兵会对下线的master进行故障转移。并且,每个哨兵对同一个master进行故障转移时,都会使用不同的配置版本号,以确保安全性。
主从故障转移的过程(failover)
选择新的主节点
从节点选举策略
当一个 Sentinel 节点准备执行故障转移时,此时, master 已经处于 ODOWN 状态,并且该哨兵节点已经从大多数哨兵节点中获得了故障转移的授权,因此,需要选择一个合适的从节点(副本)。
从节点的选举的评估标准:
-
从节点与主节点断开连接的时间;
-
从节点优先级(Replica priority);
-
已处理复制偏移量(Replication offset processed);
-
运行标识(Run ID);
首先,如果一个从节点与主节点断开连接的时间超过 \(T\),那么,该从节点会被认为是不可靠的,不适合进行故障转移,并被忽略掉,不会被用于选举。
T = (down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
注:可以通过在从节点上执行的 INFO
来查看它与主节点的断开连接时间。
其次,过滤掉网络连接不好的从节点后,选举只会从满足上述要求的剩余从节点中进行选举,并安装如下顺序进行选择:
-
从节点按照 Redis 实例配置文件(redis.conf)中的
replica-priority
配置排序,优先选择优先级较低的节点; -
如果从节点的优先级相同,则检查从节点的复制偏移量(replication offset),并选择复制偏移量更大的从节点,即从 Master 接收到更多数据的 Replica;
-
如果多个从节点具有相同的优先级,并从节点的复制偏移量也相同(从主节点复制了相同的数据),则选择 Run ID 字典序较小的从节点。
具有较低的 Run ID 对于从节点来说并不具有真正的优势,但有助于使从节点选择过程更具有确定性,而不是求助于随机地选择从节点。
在大多数情况下,replica-priority
不需要显式设置,因此,所有实例都将使用相同的默认值。除非有特定的故障转移偏好,则必须在所有实例上都设置replica-priority
,包括主节点,因为主节点可能会在未来的某个时间点成为从节点,之后,它将需要设置适当的 replica-priority
。
如果 Redis 节点的 replica-priority
被配置为 \(0\),那么, Sentinel 永远不会选择它作为新的主节点。然而,以这种方式配置的副本,仍然会被 Sentinel 重新配置,以便在故障转移后与新的 master 进行复制,唯一的区别是它永远不会成为 master 。
从副本中选择主节点
在从节点中选择出一个状态良好、数据完整的从节点之后,哨兵(Leader)会给这个从节点发送 SLAVEOF no one
命令,将这个从节点晋升为新的主节点。
在发送 SLAVEOF no one
命令之后,哨兵 leader 会以每秒一次的频率向被升级的从节点发送 INFO
命令(没进行故障转移之前,INFO 命令的频率是每十秒一次),并观察命令回复中的角色信息,当被升级节点的角色信息从原来的 slave 变为 master 时,就表明故障转移进行成功了。
此时,即使有的从节点的 reconfiguration 正在进行中,也被认为故障转移(failover)成功了,所有的 Sentinel 节点需要开始上报新的配置。
将从节点指向新的主节点
哨兵 leader 向所有从节点发送 SLAVEOF
,将它们切换为新的主节点的副本。
广播主节点变更的消息
主从切换完成后,哨兵就会向 +switch-master
频道发布新的主节点的 ip:port 的消息,这个时候,客户端就可以收到这条信息,然后,用新的主节点的 ip:port 进行通信了。
将旧的主节点变为从节点
故障转移操作最后要做的是,继续监视旧的主节点,当旧的主节点重新上线时,哨兵集群就会向它发送 SLAVEOF
命令,让它成为新的主节点的副本。
故障转移过程中存在的数据丢失问题
故障转移过程中存在两类的数据丢失问题:
-
异步复制
因为主从节点的数据复制是异步的,所以,可能有部分数据还没复制到从节点,主节点就宕机了,那么这些数据就丢失了。
-
脑裂
某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着, 这个时候,集群中就会出现两个master。
此时,虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master数据可能就会丢失。因此master在恢复的时候,会被作为一个slave挂到新的master上,自己的数据会被清空,从新的master复制数据,
解决异步复制和脑裂导致的数据丢失的问题
设置数据复制和同步的延迟时间:
min-slaves-to-write 1
min-slaves-max-lag 10
上面的配置要求至少有 1 个从节点,并且,数据复制和同步的延迟不能超过 10 秒。即如果所有的从节点数据复制和同步的延迟都超过了 10 秒,那么,此时 master 就不会再接收任何请求了。
这样的好处:
-
减少异步复制的数据丢失
如果从节点复制数据和 ack 延时太高,master 宕机后损失的数据可能会很多,那么就拒绝写请求,这样可以把 master 宕机时,由于部分数据未同步到从节点导致的数据丢失的风险,降低到可控范围内。
-
减少脑裂的数据丢失
如果一个 master 出现了脑裂,跟其他的从节点断开连接,那么上面的配置可以确保,如果不能继续给指定数量的从节点发送数据,而且从节点超过 10 秒没有回复 ack 消息,那么,就直接拒绝客户端的写请求。这样脑裂后的旧 master 就不会接受 client 的新数据,也就避免了数据丢失。
如果跟任何一个从节点断开连接,在 10 秒后发现没有从节点回复 ack,那么就拒绝新的写请求。因此在脑裂场景下,最多就丢失10秒的数据。
总结
哨兵模式的缺点:
-
当 master 挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;若是在电商网站大促的时候master给挂掉了,几秒钟损失好多订单数据;
-
哨兵模式对外只有 master 节点可以写,slave 节点只能用于读。尽管 Redis 单节点最多支持 10W 的 QPS,但是在电商大促的时候,写数据的压力全部在master上;
-
Redis 的单节点内存不能设置过大,若数据过大,在主从同步将会很慢,在节点启动的时候,时间特别长(从节点上有主节点的所有数据)。
哨兵架构几乎可以做到了我们的要实现的高可用,但是,哨兵的选举还是需要时间的,而且,中间会阻塞客户端的请求,假如我们的选举消耗了 1 秒(实际可能几秒,高则几十秒),就在这 1 秒的时候,如果客户端发起请求,那么该请求是不可用的,并且,我们的读/写的 Redis 实例,实际还是单节点的。
为了解决这些问题,可以使用 Redis集群架构:
Redis 的集群其实就是一个个小的主/从节点结合在一起,构成了我们的 Redis 集群,每个小主/从节点也就是我们的 Redis 数据分片。
参考: