Redis 哨兵机制详解

哨兵机制的概念

Redis 有三种模式:分别是主从复制、哨兵模式、集群模式,后两者可以保证高可用。

  • 哨兵(Sentinel)机制是什么
    • 哨兵机制主要用于 Redis 主从架构下的故障检测与自动主从切换。
    • 哨兵一个专门用于高可用的 Redis 组件(节点),不是用于存储数据的。
    • 哨兵不参与数据同步和读写,而是专门负责监控、故障切换和通知客户端谁是主节点。
    • 哨兵进程通常和 Redis 节点分开部署(也可以部署在同一台机器上,但进程独立)。
    • 通常运行多个哨兵实例(即哨兵集群),实现冗余和仲裁。每个哨兵都会连接至 Master 节点和所有 Slave 节点,监控它们的状态信息。
  • 哨兵(Sentinel)节点的作用是什么

    • 监控(Monitor):持续检查主节点和从节点是否存活(通过 PING 等)。
    • 通知(Notification):检测到故障后通知系统管理员或其他服务。
    • 自动故障转移(Failover):如果主节点不可用,选举一个从节点提升为新主节点。
    • 服务发现(Discovery):提供主节点的地址信息给客户端(可供自动重连);如果发生了故障转移,会通知客户端新的主节点地址。
  • 哨兵(Sentinel)节点不是从节点(Slave)

    • 哨兵节点不负责同步数据,也不参与数据读写。
    • 它只是通过 Redis 协议连接 Redis 实例,执行命令如 INFO、PING 来感知状态。
    • 哨兵节点不保存业务数据,最多保存一些监控状态(内存中)。
  • 为什么需要哨兵(Sentinel)机制

    • Redis 的主从复制机制主要用于实现数据备份和读请求的负载分担,但它本身并不具备自动容错和主节点自动切换的能力。因此,单纯依赖主从复制并不能保证系统的高可用性。具体表现如下:
      • 需要人工介入:当主节点发生故障时,Redis 本身无法自动完成故障切换,需要运维人员手动将某个从节点提升为新的主节点,并重新配置其他从节点同步新的主节点。
      • 单点故障风险:主节点作为写操作的唯一入口,一旦宕机,系统的写操作将完全中断,严重影响服务可用性。
      • 主节点写入能力受限:Redis 是单线程模型,主节点的写入吞吐受限于单机性能,无法横向扩展。
      • 单机节点存储容量有限:Redis 通常运行在内存中,主节点的物理内存限制了存储能力。
    • Redis 哨兵机制应运而生,用于增强 Redis 在主从架构下的高可用性。它具备以下能力:
      • 哨兵节点自动监控 Redis 实例状态(包括主节点和从节点)。
      • 主节点宕机时,自动完成主从切换(Failover)。
      • 通知支持 Sentinel 机制的客户端连接新的主节点。
      • 协助管理 Redis 主从结构并确保一致性。
    • 若需要进一步提高 Redis 的可用性与扩展性,还可以使用 Redis Cluster(集群),它支持数据分片(水平扩展),具备原生的多主多从架构、高可用、以及容错能力。

哨兵机制的原理

Redis 哨兵机制是通过在独立的哨兵节点上运行特定的哨兵进程来实现的。这些哨兵进程监控主从节点的状态,并在出现故障时自动完成故障转移,并通知应用方,实现高可用性。

  • (1) 哨兵选举:

    • 在启动时,每个哨兵节点都会参与选举,其中一个哨兵节点会被选为领导者(Leader),负责协调其他哨兵节点执行故障转移。选举过程如下:
      • 每个在线的哨兵节点都有资格成为领导者。当某个哨兵判断主节点不可用后,会向其他哨兵节点发送 is-master-down-by-addr 命令,请求判断主节点状态并征求选票,希望被选为本轮的领导者。
      • 其他哨兵节点在收到该命令后,会根据自身判断和是否已经投票的情况,决定是否同意对方成为领导者(每个哨兵在同一轮选举中只能投票一次)。
      • 如果某个哨兵节点获得的选票数达到或超过 总哨兵节点数 / 2 + 1(即超过半数),则该哨兵节点将成为本轮选举的领导者;如果未能获得足够票数,则会进入下一轮选举,直到选出领导者为止。
  • (2) 哨兵监控主从节点:

    • 哨兵节点通过发送命令周期性地检查主从节点的健康状态,包括主节点是否在线、从节点是否同步等。
    • 如果哨兵节点发现主节点不可用,它会触发一次故障转移操作,而且是由哨兵领导者负责处理主节点的故障转移。
  • (3) 哨兵执行故障转移:

    • 一旦主节点被判定为不可用,哨兵节点会执行故障转移操作。它会从当前的从节点中选出一个新的主节点,并将其他从节点切换到新的主节点。这样,缓存系统可以继续提供服务,而无需人工介入。
    • 故障转移过程:
      • 由哨兵节点定期监控主节点是否出现故障,哨兵节点会定期向主节点发送心跳 PING 来确认主节点是否存活。
      • 如果主节点在 “一定时间范围” 内不响应 PONG 或者是回复了一个错误消息,那么这个哨兵节点会主观地(单方面地)认为这个主节点已经不可用了。
    • 确认新主节点:
      • 过滤掉不健康的从节点(如已下线、网络断连、长时间未响应哨兵 PING 命令的节点)。
      • 在剩余的健康从节点中,优先选择优先级(Priority)最高的节点。
      • 如果有多个从节点优先级相同,则选择复制偏移量(Replication Offset)最大的节点,即数据最接近原主节点的从节点。
      • 若优先级和复制偏移量都相等,则选择节点 ID 字典序最小的节点作为新主节点。
  • (4) 客户端重定向:

    • 当主节点出现故障时,哨兵节点会自动发起主从切换(故障转移),选举一个新的从节点作为新的主节点。
    • 哨兵节点不会直接通知客户端新的主节点地址,而是提供一个服务发现机制。客户端需要通过支持哨兵机制的客户端,从哨兵节点动态获取当前的主节点地址。
    • 这样一来,客户端可以在主节点切换后,通过哨兵节点重新获取主节点信息,从而无感知地完成主节点重连,保证业务连续性。
    • 此外,哨兵节点还会持续监控所有主节点和所有从节点的运行状态,如果某个从节点出现故障,哨兵节点会将其标记为下线;一旦从节点恢复,哨兵节点会自动将其重新加入主从复制架构,并使其同步当前主节点的数据,以维持整个架构的完整性。

哨兵的部署架构

Redis Sentinel(哨兵)本身是一个分布式系统,通常以哨兵集群的形式部署,多个哨兵节点之间可以协同工作,保障系统的高可用性(如下图所示)。

  • 哨兵(Sentinel)故障转移的核心概念
概念解释作用
quorum(法定票数)Master 节点从主观下线(sdown)到客观下线(odown)所需的同意哨兵数(即最少 N 个哨兵同时判断 Master 为宕机)确保哨兵对 Master 宕机的判断具有一定共识,避免误判
majority(多数哨兵)执行故障转移操作时,要求同意执行故障转移的哨兵数量必须达到总数的一半以上,否则不会执行故障转移为了避免「脑裂」现象,即多个哨兵在不同网络分区中同时尝试进行故障转移,导致系统不一致或混乱
  • 哨兵(Sentinel)的部署架构是什么

    • Redis Sentinel(哨兵)本身是一个分布式系统,通常以哨兵集群的形式部署,多个哨兵节点之间可以协同工作,保障系统的高可用性。
      • 哨兵通常以集群的形式部署,这样是为了保证哨兵的高可用性
        • 哨兵集群要求至少需要部署 3 个哨兵实例,否则可能无法保证故障转移的正常执行,同时也为了实现多数投票机制,并提高容错能力。
        • 哨兵集群 + Redis 主从架构能够提供高可用性,但无法做到数据零丢失。故障转移过程中可能存在数据未完整同步到从节点的风险,因此仅适用于对可用性要求高、但允许少量数据丢失的场景。
        • 由于哨兵 + Redis 主从是一种相对复杂的部署架构,建议在测试环境和生产环境中都进行充分的测试与故障演练,确保系统在各种异常情况下都能稳定运行。
      • 在发生故障转移时,是否将一个主节点判定为宕机,必须经过多数哨兵节点(Quorum)的同意。这涉及到分布式选举机制,用于确保故障转移的判断和执行具备一致性和可靠性。
      • 即使部分哨兵节点发生故障,哨兵集群仍然能够正常工作。这是因为哨兵本身作为高可用机制的一部分,必须具备容错能力,若其自身是单点的,那就违背了其设计初衷。
      • 目前使用的是 Sentinel 2.x 版本。与 Sentinel 1.x 相比,Sentinel 2.x 重写了大量核心代码,主要目的是简化故障转移流程、提升算法健壮性和系统稳定性,使其更适用于生产环境中的高可用场景。
  • 为什么 Redis 哨兵集群只有 2 个节点会无法正常工作

    • 以部署了 2 个哨兵实例的场景为例(配置:quorum = 1):
      1
      2
      3
      4
      +----+         +----+
      | M1 |---------| R1 |
      | S1 | | S2 |
      +----+ +----+
    • 当主节点 M1 宕机时,只要 S1 和 S2 中任意一个哨兵认为 M1 宕机,就可以发起主观下线(Subjective Down,简称 sdown)判断。接着,两个哨兵会通过选举机制,选举出其中一个哨兵来执行故障转移操作。
    • 但是,哨兵系统执行真正的故障转移时,还需要满足 majority 要求,也就是多数哨兵节点同意执行故障转移。例如:
      • 2 个哨兵时,要求 majority = 2
      • 3 个哨兵时,要求 majority = 2
      • 4 个哨兵时,要求 majority = 3
      • 5 个哨兵时,要求 majority = 3
    • 因此在 2 个哨兵节点的场景中,只有当两个哨兵都正常运行时,才满足 majority 的要求,才允许执行故障转移操作。
    • 如果运行主节点 M1 和哨兵 S1 的那台机器宕机了,意味着主节点 M1 和哨兵 S1 同时失效,只剩下从节点 R1 和哨兵 S2 仍在运行。此时虽然还有一个哨兵存在(S2),但由于无法满足 majority 要求,故障转移将不会被执行,导致缓存系统始终处于不可用状态。
  • Redis 经典的 3 节点哨兵集群架构

    • 以部署了 3 个哨兵实例的场景为例(配置:quorum = 2):
      1
      2
      3
      4
      5
      6
      7
      8
      9
             +----+
      | M1 |
      | S1 |
      +----+
      |
      +----+ | +----+
      | R2 |----+----| R3 |
      | S2 | | S3 |
      +----+ +----+
    • 如果主节点 M1 所在的机器宕机,意味着主节点 M1 和哨兵 S1 同时失效,剩下的两个哨兵 S2 和 S3 仍在运行。此时:
      • 哨兵 S2 和 S3 可以一致地判断主节点 M1 宕机了(满足 quorum = 2),从而形成客观下线(odown)判断;
      • 接着,S2 和 S3 两个哨兵会通过选举机制,选举出其中一个哨兵负责执行故障转移操作;
      • 因为 3 个哨兵的 majority 要求为 2,而当前恰好有 2 个哨兵仍然存活,所以满足故障转移所需的条件。
    • 因此,在这种经典的 3 节点哨兵集群架构下,即使一台机器宕机,只要剩余的 2 个哨兵还在正常运行,依然可以完成故障转移,从而保证整个哨兵集群的高可用性。

特别注意

  • 无论 Redis 是一主一从、一主多从的复制架构,都可以使用 Redis 的哨兵机制,官方更推荐使用一主多从的复制架构,这样可用性更高。

哨兵机制的使用

在 SpringBoot 项目中,使用 Lettuce 客户端连接 Redis 的一主多从 + 哨兵模式时,只需要在 application.yml 正确配置哨兵信息,SpringBoot 就会自动识别并创建 Lettuce 连接。

  • SpringBoot 配置文件示例(application.yml
1
2
3
4
5
6
7
8
9
10
spring:
redis:
sentinel:
master: mymaster # 主节点名称,对应哨兵配置中的 sentinel monitor 的名称
nodes: # 哨兵节点列表(ip:port)
- 192.168.1.101:26379
- 192.168.1.102:26379
- 192.168.1.103:26379
password: yourRedisPassword # Redis 认证密码(如果开启了)
database: 0
  • 若希望使用从节点读取数据(主节点默认可以读写,但从节点只读不可写),可以使用以下 SpringBoot 配置信息
1
2
3
4
5
6
7
8
9
10
11
12
spring:
redis:
sentinel:
master: mymaster # 主节点名称,对应哨兵配置中的 sentinel monitor 的名称
nodes: # 哨兵节点列表(ip:port)
- 192.168.1.101:26379
- 192.168.1.102:26379
- 192.168.1.103:26379
password: yourRedisPassword # Redis 认证密码(如果开启了)
database: 0
lettuce:
read-from: REPLICA_PREFERRED
Lettuce 的配置值含义
MASTER所有请求都从主节点读取(默认)
MASTER_PREFERRED优先主节点,主节点不可用时才从从节点读取
REPLICA(或 SLAVE所有请求都从从节点读取
REPLICA_PREFERRED优先从从节点读取,从节点不可用时回退到主节点
NEAREST从网络延迟最小的节点读取(需要集群拓扑支持)
  • Redis 哨兵配置文件示例(redis-sentinel.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
# 监控名为 mymaster 的主节点,IP 是 192.168.1.100,端口 6379,
# 至少有 2 个哨兵同时判断该主节点不可达时,才会被判定为主观下线(sdown)
sentinel monitor mymaster 192.168.1.100 6379 2

# 如果 5000 毫秒(5 秒)内没有收到主节点的响应,哨兵就认为主节点已主观下线
sentinel down-after-milliseconds mymaster 5000

# 整个故障转移(Failover)过程的超时时间:10 秒
# 包括选举新主节点、通知其他从节点进行复制、通知客户端更新配置等
sentinel failover-timeout mymaster 10000

# 故障转移时,最多同时有 1 个从节点并行地从新的主节点复制数据(同步)
sentinel parallel-syncs mymaster 1
  • 引入 Lettuce 依赖,使用 Lettuce 的默认连接工厂
1
2
3
4
5
<!-- SpringBoot 默认使用 Lettuce 作为 Redis 客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • Redis 连接验证代码
1
2
3
4
5
6
7
8
9
@Autowired
private StringRedisTemplate stringRedisTemplate;

@PostConstruct
public void testConnection() {
stringRedisTemplate.opsForValue().set("testKey", "hello from sentinel");
String value = stringRedisTemplate.opsForValue().get("testKey");
System.out.println("Redis 返回: " + value);
}
  • SpringBoot 配置(application.yml)的注意事项
    • 主从节点的 IP 和端口不需要配置,哨兵模式下 Lettuce 客户端能自动发现主从拓扑结构。
    • mymaster 一定要和哨兵配置文件(redis-sentinel.conf)里设置的一致。
    • Lettuce 默认支持哨兵模式,一般不需要额外配置;但是,如果哨兵模式使用 SSL,则需要显式配置连接工厂。
    • SpringBoot 的默认连接池支持读写分离,只需要配置 Lettuce 的 read-from 属性即可生效。
    • 在 Redis 的主从复制或者集群架构中,主节点可以读写,但从节点默认是只读的,即可以响应读请求(如 GETMGET 等),但不能写入数据。

特别注意

无论 Redis 是一主一从、一主多从还是集群架构,Redis 的主节点都可以读写,从节点默认是可以读(只读,不能写入);但如果想实现真正的读写分离或者读负载均衡,还需要在客户端进行配置或开发支持(比如 Jedis、Lettuce 都支持手动配置是否访问从节点),因为 Redis 本身不会自动将读请求发送给从节点。由于 Redis 的主从同步可能存在延迟,如果业务对读取一致性要求较高(如读取后马上更新),那么就不要使用从节点读取数据。

哨兵机制的问题

如何避免脑裂现象

  • Redis 哨兵集群的脑裂现象是指什么

    • 指在出现网络分区或者部分哨兵节点失联的情况下,多个哨兵节点在没有达到 majority(多数哨兵)共识的前提下,分别认为 Master 节点宕机并发起故障转移,导致出现两个或多个 Master 节点,从而造成数据不一致或系统混乱。
    • 值得一提的是,除了主从同步延迟外,脑裂现象也会导致 Redis 集群丢失部分缓存数据。
  • Redis 哨兵集群如何避免脑裂现象

    • 设置合适的 quorum(法定票数),即某个哨兵要认为主节点下线,必须要有至少 quorum 个哨兵达成共识。
    • 真正的故障转移必须经过 majority 个哨兵(多数哨兵)投票通过,避免少数哨兵单方面误判。
    • 因此,只要哨兵总数为奇数(如 3 个或 5 个),且大多数哨兵能互通,就不会发生脑裂现象。
      • 举个例子,假设集群中有 3 个哨兵,并配置 quorum = 2,那么:
        • 只有至少 2 个哨兵都判断主节点不可用,才会触发故障转移;
        • 此时,还需要过半(即至少 2 个)的哨兵同意发起故障转移,才能选出新的主节点;
        • 如果由于网络问题分区成 1 + 2 的两组哨兵,单独的哨兵将无法满足 quorummajority 条件,这样就不会误判,从而避免脑裂。
概念解释作用
quorum(法定票数)Master 节点从主观下线(SDOWN)到客观下线(ODOWN)所需的同意哨兵数(即最少 N 个哨兵同时判断 Master 为宕机)确保哨兵对 Master 宕机的判断具有一定共识,避免误判
majority(多数哨兵)执行故障转移操作时,要求同意执行故障转移的哨兵数量必须达到总数的一半以上,否则不会执行故障转移为了避免「脑裂」现象,即多个哨兵在不同网络分区中同时尝试进行故障转移,导致系统不一致或混乱

网络分区是指什么

网络分区(Network Partition)是指由于网络故障,集群中的一部分节点之间无法正常通信,被分隔成了两个或多个 "孤岛",每个孤岛只能看到自身可达的节点,看不到其他节点。在 Redis 的哨兵集群架构中,网络分区通常指的是:部分哨兵与主节点失去连接、部分哨兵之间失去通信、主从节点之间断联。这些都可能导致:误判主节点下线、多个哨兵同时发起故障转移、出现多个主节点(脑裂)。

如何避免数据丢失

Redis 的哨兵机制主要用于主从架构下的故障检测与自动主备切换,但在某些特殊场景下,哨兵机制可能会导致数据丢失,主要包括以下两种情况:

  • (1) 异步复制导致的数据丢失

    • Redis 主从节点之间的数据同步是异步复制,这意味着主节点写入的数据不会立即同步到从节点。
      • 当主节点(Master)宕机时,可能仍有部分数据尚未同步写入到从节点(Slave)。
      • 此时,如果哨兵进行故障转移,从节点被提升为新的主节点,那么这些未同步的数据将永久丢失。
    • 这种情况的本质是主从延迟造成的数据不一致,属于设计上的权衡(异步复制换取更高性能和低延迟)。
  • (2) 脑裂(Split-Brain)导致的数据丢失

    • 脑裂指的是:主节点与其他哨兵和从节点之间发生网络分区,导致其在局部网络中 “孤岛运行”,而在整体视角下却被认为已宕机。
      • 在发生网络分区期间,哨兵可能判定主节点不可用(ODOWN - 客观不可用),并发起故障转移,将某个从节点提升为新的主节点。
      • 但由于旧主节点实际上仍在运行,客户端可能仍将数据写入旧主节点,形成了两个 “主节点”(双主)。
      • 当网络恢复时,旧主节点会被哨兵强制转为从节点,并从新主节点复制数据,这会导致旧主节点上的数据被清空。
    • 因此,在发生网络分区的这段时间内,写入旧主节点的数据会丢失,因为它从未被同步到新主节点上,并且在旧主节点恢复后被覆盖掉。

Redis 提供以下两个配置项用于控制写请求行为,从而减少异步复制和脑裂导致的数据丢失。

  • min-slaves-to-write 1:要求至少有 1 个从节点处于正常连接状态,主节点才允许写入数据。
  • min-slaves-max-lag 10:要求从节点的复制延迟(ACK 返回的时间)不能超过 10 秒,主节点才允许写入数据。

当主节点检测到可以正常连接的从节点数量不足,或者所有从节点的复制延迟都超过 10 秒,那么主节点将拒绝客户端的写请求。

  • (1) 减少异步复制导致的数据丢失

    • Redis 的主从复制是异步的。如果主节点宕机,而有些数据尚未同步到从节点,那么这些数据将永久丢失。
    • 通过设置 min-slaves-max-lag,主节点可以感知从节点复制数据的延迟。如果从节点响应太慢(比如都超过了 10 秒),主节点会拒绝客户端的写入请求,防止继续写入大量数据而无法同步,从而将主节点宕机时可能丢失的数据限制在一个可控的范围(如 10 秒)内。
  • (2) 减少脑裂导致的数据丢失

    • 在出现脑裂的情况下,客户端可能仍向旧主节点写入数据,形成两个主节点(双主),导致数据不一致和丢失。
    • 通过 min-slaves-to-writemin-slaves-max-lag 这两个配置项,可以防止脑裂主节点继续接受写请求:
      • 如果旧主节点失去了所有从节点的连接;
      • 且在 min-slaves-max-lag 时间(如 10 秒)内未收到任何从节点的 ACK 消息;
      • 那么主节点将自动停止接受写请求。
    • 这样,即使发生脑裂,旧主节点也会在 10 秒内拒绝写入数据,最多只会丢失 10 秒的数据,大大降低了数据丢失的风险。

当 Redis 主节点拒绝写请求时,客户端可以采取如下策略进行容灾处理:

  • (1) 客户限流处理
    • 对接口请求进行限流处理,减慢请求涌入的速度,防止请求堆积或爆发式增长。
  • (2) 异步重试机制
    • 将待写入的数据缓存在本地磁盘或者 Kafka 消息队列中。
    • 客户端定时从本地磁盘或者 Kafka 队列中获取数据(例如每隔 10 分钟),然后尝试将数据重新写回 Redis 主节点。