哨兵模式 - 提供 Redis 高可用性的守护者

引言

在现代的分布式系统中,高可用性是至关重要的。尤其是在面对节点故障和网络中断时,系统必须能够自动检测并快速地进行故障恢复,以确保数据的安全性和可靠性。Redis哨兵模式作为一个强大的机制,通过提供自动化的监控和故障转移功能,成为了保护分布式Redis数据的守护者。
本文将带您深入了解Redis哨兵模式的搭建过程、核心工作原理以及它如何确保高可用性。我们将揭示哨兵节点如何主动发现和监控数据库中的主节点和从节点,并在主节点故障时协调进行故障转移的过程。
我们将详细讨论哨兵节点与主节点之间的通信流程,包括订阅频道、心跳检测以及故障通知。您将了解到哨兵节点是如何通过定期与主节点通信来监控其状态,并在必要时发起故障切换操作。
此外,我们还将探讨哨兵模式的配置和部署细节。您将学习如何设置哨兵节点、配置主从复制以及进行故障切换,并确保整个系统的可用性和稳定性。
哨兵模式不仅提供了高可用性,还具备一定程度的水平扩展能力,使得您的Redis部署更具弹性和可伸缩性。我们将讨论哨兵模式的适用场景以及与其他Redis特性的结合使用。
无论您是一个Redis初学者还是一个有经验的用户,本文将为您提供全面的指南,帮助您深入了解Redis哨兵模式,并在您的分布式环境中实现高可用性和故障恢复。
让我们一起开始这段关于Redis哨兵模式的探索之旅吧!

哨兵模式

Redis Sentinel是一种用于高可用性(High Availability)的解决方案,它通过在 Redis 部署中添加额外的 Sentinel 节点来监控和管理 Redis 主从服务器。当主服务器出现故障时,Sentinel 可以自动进行故障切换,选择一个合适的从服务器提升为新的主服务器。

优点

  1. 高可用性:Redis 哨兵模式确保了分布式 Redis 数据库的高可用性。通过监控主节点和从节点的状态,并在主节点发生故障时自动执行故障转移操作,哨兵模式能够使系统在主节点故障的情况下继续正常运行。
  2. 自动化故障恢复:哨兵模式实现了自动化的故障检测和故障转移。当主节点出现故障时,哨兵节点会自动发现并通知其他节点,协调选举出新的主节点,并将客户端重定向到新的主节点,以实现快速的故障恢复。
  3. 集中式监控与管理:哨兵模式提供了集中式的监控和管理。通过哨兵节点,您可以轻松监控主节点和从节点的状态、配置信息以及故障转移的过程。这样,您可以更好地了解系统的健康状况,并及时采取必要的措施。
  4. 灵活性和可扩展性:哨兵模式提供了灵活性和可扩展性。您可以动态地添加或删除哨兵节点,以适应系统的变化。此外,哨兵模式还支持添加更多的从节点,并在需要时进行扩容,以应对系统的负载需求。

解决了主从复制的哪些问题

  1. 单点故障:主从复制中的一个主要缺点是主节点的单点故障问题。当主节点发生故障时,传统主从复制无法自动切换到新的主节点。而哨兵模式通过监控主节点的状态并自动进行故障转移,解决了单点故障的问题,保证了系统的高可用性。
  2. 配置管理复杂:主从复制中,管理从节点的复制关系需要手动配置并维护。而哨兵模式通过哨兵节点的自动发现和监控,可以自动管理各个从节点的复制关系,简化了配置管理的复杂性。
  3. 故障恢复慢:在传统的主从复制中,当主节点发生故障时,需要手动干预来重新配置从节点以确保新的主节点。而哨兵模式通过自动故障转移,能够实现快速的故障恢复,减少系统停机时间。

配置过程

我在 深入了解Redis主从同步:高性能数据复制的利器中阐述了如何配置Redis主从复制,哨兵算是对主从复制的补充,这里就不再讲解主从复制的配置过程,演示环境中我搭建了一套1主3从的环境,分别是:

1
2
3
4
5
主:127.0.0.1:6382
从1:127.0.0.1:6379
从2:127.0.0.1:6380
从3:127.0.0.1:6381
哨兵节点:127.0.0.1:26379

哨兵节点其实就是一个Redis服务,只需要修改其配置文件的其中几项:

1
2
3
4
5
6
port 26379
bind 0.0.0.0
daemonize yes # 可选 后台运行
sentinel monitor mymaster MASTER_IP MASTER_PORT QUORUM # sentinel monitor 主节点名称 主节点ip 主节点端口 进行故障切换时所需的投票数量。
sentinel auth-pass mymaster YOUR_MASTER_PASSWORD # 主节点密码
sentinel down-after-milliseconds mymaster 5000 # 故障切换的时间

这里详细说一下QUORUM参数
sentinel monitor 命令用于告知哨兵节点来监视指定的 Redis 主服务器。其中,QUORUM 是一个参数,用于设置哨兵节点在进行故障切换时所需的投票数量。
具体来说,QUORUM 参数定义了在 Redis 主服务器被判定为不可用之后,需要多少个哨兵节点认可才能执行故障切换。这意味着需要达到的最小投票数量以确保这次故障切换的可靠性。
而实际上,QUORUM 参数的计算方式与哨兵节点的数量有关,它的计算方式为 (哨兵节点数量 / 2) + 1。
举个例子,假设 Redis Sentinel 集群中有 3 个哨兵节点,那么 (3 / 2) + 1 = 2,所以在这个例子中 QUORUM 的值应该设置为 2。也就是说,当两个或以上的哨兵节点认为主服务器不可用时,才会执行故障切换。
通过设置 QUORUM 参数,可以确保在故障切换时有足够的哨兵节点达成一致意见,以避免错误的故障切换。

配置完成后可以用以下命令启动哨兵节点,注意,后面的--sentinel

1
redis-server /path/to/sentinel.conf --sentinel

如果没有遗漏,当下配置启动流程就结束了,接下来对启动日志进行一下分析(主从的日志流程在主从配置的文章里以及见过了,这里只大概注释一下)

主节点日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[7980] 21 Jul 11:01:21.729 # Server initialized
[7980] 21 Jul 11:01:21.730 * DB loaded from disk: 0.000 seconds
[7980] 21 Jul 11:01:21.730 * Ready to accept connections
# 收到一条新的连接 并同步自身数据给6381
[7980] 21 Jul 11:01:28.930 * Replica 127.0.0.1:6381 asks for synchronization
[7980] 21 Jul 11:01:28.930 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '77e20dba6116342d87205890f5803accd7cf9e88', my replication IDs are '830db770838383cbb0ce503ba2c10df04e309250' and '0000000000000000000000000000000000000000')
[7980] 21 Jul 11:01:28.930 * Starting BGSAVE for SYNC with target: disk
[7980] 21 Jul 11:01:28.933 * Background saving started by pid 14936
[7980] 21 Jul 11:01:29.028 # fork operation complete
[7980] 21 Jul 11:01:29.036 * Background saving terminated with success
[7980] 21 Jul 11:01:29.037 * Synchronization with replica 127.0.0.1:6381 succeeded
# 收到一条新的连接 并同步自身数据给6380
[7980] 21 Jul 11:01:44.923 * Replica 127.0.0.1:6380 asks for synchronization
[7980] 21 Jul 11:01:44.923 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '77e20dba6116342d87205890f5803accd7cf9e88', my replication IDs are '67b82c91f0e945546a978eaca3fa402335aa698d' and '0000000000000000000000000000000000000000')
[7980] 21 Jul 11:01:44.923 * Starting BGSAVE for SYNC with target: disk
[7980] 21 Jul 11:01:44.927 * Background saving started by pid 2664
[7980] 21 Jul 11:01:45.019 # fork operation complete
[7980] 21 Jul 11:01:45.026 * Background saving terminated with success
[7980] 21 Jul 11:01:45.027 * Synchronization with replica 127.0.0.1:6380 succeeded
# 收到一条新的连接 并同步自身数据给6379
[7980] 21 Jul 11:02:03.812 * Replica 127.0.0.1:6379 asks for synchronization
[7980] 21 Jul 11:02:03.812 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '77e20dba6116342d87205890f5803accd7cf9e88', my replication IDs are '67b82c91f0e945546a978eaca3fa402335aa698d' and '0000000000000000000000000000000000000000')
[7980] 21 Jul 11:02:03.812 * Starting BGSAVE for SYNC with target: disk
[7980] 21 Jul 11:02:03.816 * Background saving started by pid 8684
[7980] 21 Jul 11:02:03.955 # fork operation complete
[7980] 21 Jul 11:02:03.966 * Background saving terminated with success
[7980] 21 Jul 11:02:03.967 * Synchronization with replica 127.0.0.1:6379 succeeded

哨兵日志:

1
2
[8616] 21 Jul 11:01:36.281 # Sentinel ID is 798b5039b6784e076d84b772ed6eefa43a6fb7f9
[8616] 21 Jul 11:01:36.281 # +monitor master mymaster 127.0.0.1 6382 quorum 1

如果我们把其中一个从节点(例如6379)下线,再来观察哨兵的日志和主节点日志
主节点日志:

1
2
# 丢失与6379的连接
[7980] 21 Jul 11:14:59.319 # Connection with replica 127.0.0.1:6379 lost.

哨兵日志:

1
2
# 从节点被标记为主观下线
[11992] 21 Jul 11:15:04.457 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382

哨兵节点收到从节点状态变化为+sdown的事件通知时,表示从节点被标记为主观下线。在哨兵模式中,即某个哨兵节点单独判断该节点处于下线状态。
此时再次启动被手动下线的从节点(6379)观察哨兵的日志:

1
[11992] 21 Jul 11:18:16.000 * +reboot slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382

哨兵监测到从节点(6379)又重新上线,接下来我们再把主节点6382下线,再观察一下哨兵以及其他从节点的日志
哨兵日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 主节点主观下线
[11992] 21 Jul 11:44:45.564 # +sdown master mymaster 127.0.0.1 6382
# 主节点客观下线
[11992] 21 Jul 11:44:45.564 # +odown master mymaster 127.0.0.1 6382 #quorum 1/1
# 广播,通知其它哨兵节点重新选举主节点 这个命令很重要
[11992] 21 Jul 11:44:45.564 # +new-epoch 3
[11992] 21 Jul 11:44:45.564 # +try-failover master mymaster 127.0.0.1 6382
# 798b5039b6784e076d84b772ed6eefa43a6fb7f9: 这是一个用于选举领导者的唯一标识符,通常是一个节点的ID。
# 3: 这是一个数字,表示该节点投票的计数。
[11992] 21 Jul 11:44:45.565 # +vote-for-leader 798b5039b6784e076d84b772ed6eefa43a6fb7f9 3
# 再次尝试连接之前的主节点
[11992] 21 Jul 11:44:45.565 # +elected-leader master mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.565 # +failover-state-select-slave master mymaster 127.0.0.1 6382
# 让其他节点尝试重新连接6382节点
[11992] 21 Jul 11:44:45.628 # +selected-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.628 * +failover-state-send-slaveof-noone slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.723 * +failover-state-wait-promotion slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.930 # +promoted-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.930 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:45.993 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:46.594 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:47.612 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:47.691 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:48.391 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
[11992] 21 Jul 11:44:48.391 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
# 故障转移结束
[11992] 21 Jul 11:44:48.470 # +failover-end master mymaster 127.0.0.1 6382
# 切换到新的节点为 6379
[11992] 21 Jul 11:44:48.470 # +switch-master mymaster 127.0.0.1 6382 127.0.0.1 6379
# 通知其余节点切换
[11992] 21 Jul 11:44:48.470 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
[11992] 21 Jul 11:44:48.470 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
[11992] 21 Jul 11:44:48.470 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6379
[11992] 21 Jul 11:44:53.477 # +sdown slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6379

new-epoch的作用:“new-epoch” 是一个关键的命令和概念,用于实现哨兵之间的协调,以便决定新的主节点和重新配置哨兵。
当哨兵集群发现主节点失效时,它们需要达成共识,选择一个新的主节点来继续提供服务。为了协调这个过程,哨兵会使用 “new-epoch” 命令。
“new-epoch” 命令的作用是发起一个新的纪元(epoch)。纪元就像是一个集群范围内的逻辑时钟,用于协助哨兵节点之间的通信和决策。在新的纪元中,哨兵节点会重新竞选领导者,并基于多数选票来决定新的主节点。
当一个哨兵节点执行 “new-epoch” 命令时,它会向其他哨兵节点广播这个消息,携带着一个新的纪元号。其他哨兵节点会接收到这个消息,并更新自己的纪元号,并参与新的主节点选举过程。
通过 “new-epoch” 命令,哨兵集群能够在失效事件发生后,重新组织和协调集群状态,并最终决定新的主节点,以确保高可用性和持久性。
具体流程如下:

  1. 在哨兵节点中的领导者(leader)检测到主节点失效。
  2. 领导者发送 “new-epoch” 命令到其他哨兵节点,通知它们当前进入一个新的纪元。
  3. 其他哨兵节点收到 “new-epoch” 命令后,会更新自己的纪元号,以便与领导者保持一致。
  4. 在新的纪元中,每个哨兵节点会为自己选举一个领导者。
  5. 新选出的领导者带领其他哨兵节点重新进行主从切换和配置更新的过程,以选择新的主节点,并确保整个集群的可用性。

其中一条从节点的日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 收到切换主节点通知及主节点信息
[12520] 21 Jul 11:44:45.786 * MASTER <-> REPLICA sync started
[12520] 21 Jul 11:44:45.994 * REPLICAOF 127.0.0.1:6379 enabled (user request from 'id=6 addr=127.0.0.1:58982 fd=12 name=sentinel-798b5039-cmd age=1950 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=281 qbuf-free=32487 obl=36 oll=0 omem=0 events=r cmd=exec')
[12520] 21 Jul 11:44:45.996 # CONFIG REWRITE executed with success.
[12520] 21 Jul 11:44:46.895 * Connecting to MASTER 127.0.0.1:6379
[12520] 21 Jul 11:44:46.895 * MASTER <-> REPLICA sync started
[12520] 21 Jul 11:44:46.895 * Non blocking connect for SYNC fired the event.
[12520] 21 Jul 11:44:46.896 * Master replied to PING, replication can continue...
[12520] 21 Jul 11:44:46.896 * Trying a partial resynchronization (request 67b82c91f0e945546a978eaca3fa402335aa698d:171233).
# 切换完成
[12520] 21 Jul 11:44:46.897 * Successful partial resynchronization with master.
[12520] 21 Jul 11:44:46.897 # Master replication ID changed to e296087c3ca7cfa5dc2fc0e4ac9699b92942491f
[12520] 21 Jul 11:44:46.897 * MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.

此时可以观察一下各个Redis服务的配置文件,会发现配置文件的内容页发生了相应的变更

哨兵模式故障切换与恢复细节

  1. Sentinel 哨兵节点订阅频道:
    哨兵节点向主节点发送 SUBSCRIBE __sentinel__:hello 命令,订阅 __sentinel__:hello 频道。这个频道用于哨兵节点之间进行通信和状态更新。
  2. 哨兵节点发送 PING 命令:
    哨兵节点向主节点发送 PING 命令,检查主节点是否存活。这个命令用于测试主节点是否正常响应。
  3. 主节点响应 PONG
    如果主节点正常工作,它会返回响应 PONG 给哨兵节点,表示主节点正常运行。
  4. 主节点发布状态更新消息:
    主节点会定期发送状态更新消息到 __sentinel__:hello 频道,告知哨兵节点自己的状态,如健康状况、配置变更等。
  5. 哨兵节点发送 SENTINEL is-master-down-by-addr 命令:
    哨兵节点通过向主节点发送 SENTINEL is-master-down-by-addr <master-name> <ip> <port> 命令,检查主节点是否宕机。其中 <master-name> 是主节点的名称,<ip><port> 是主节点的地址信息。
  6. 主节点响应 +down+odown
    如果主节点宕机或不可达,主节点会返回响应 +down+odown 给哨兵节点。这表示主节点已经下线或被其他哨兵节点确认为下线。
  7. 哨兵节点发送故障通知:
    一旦哨兵节点确认主节点下线,它会发送故障通知到 __sentinel__:hello 频道,通知其他哨兵节点和客户端主节点的下线状态。
  8. 故障切换和选举:
    当多数哨兵节点都确认主节点下线时,它们会发起故障切换过程,并进行新的主节点选举。选举过程包括评估从节点的复制健康状态和选择新的主节点。
  9. 故障切换完成和配置更新:
    一旦新的主节点被选举出来,哨兵节点会更新其本地配置,并通过发布-订阅机制向其他哨兵节点和客户端广播新的主节点的位置。客户端可以连接新的主节点,并继续进行操作。

未完待续…