返回文章列表

Sentinel 高可用

系统梳理 Redis Sentinel 的监控、通知、服务发现、SDOWN、ODOWN、quorum、多数派、Leader 选举、故障转移、网络分区、RTO、RPO 与 Go 客户端接入。

第 16 章:Sentinel 高可用

1. 本章定位

Redis 主从复制解决了数据副本问题,但仅有主从复制并不会自动完成故障检测、主节点选举、角色切换和客户端重定向。Redis Sentinel 正是用于补齐这些能力的高可用组件。

Sentinel 面向非 Redis Cluster 的单主多副本部署,提供四类核心能力:

  1. 持续监控主节点、副本和其他 Sentinel。
  2. 在节点异常时产生通知。
  3. 主节点故障后自动选择副本并完成故障转移。
  4. 为客户端提供当前主节点地址,实现服务发现。

Sentinel 不负责数据分片,也不提供强一致复制、零数据丢失或备份能力。它解决的是“一个 Redis 主节点发生故障后,系统如何自动恢复可写服务”,而不是“如何把超大数据集拆到多个节点”。 (Redis)


2. 学习目标

完成本章后,应能够:

  1. 解释 Sentinel 的监控、通知、故障转移和服务发现能力。
  2. 准确区分主观下线 SDOWN 与客观下线 ODOWN。
  3. 解释 quorum 与 Sentinel 多数派之间的区别。
  4. 描述 Sentinel Leader 选举及完整故障转移流程。
  5. 说明 Sentinel 如何选择待提升副本。
  6. 分析网络分区、脑裂、RTO 和 RPO。
  7. 使用 redis.NewFailoverClient 编写支持 Sentinel 的 Go 客户端。
  8. 设计一个至少包含三个 Sentinel、跨独立故障域的生产拓扑。

2.1 本章边界与跳转

本章是 Sentinel 高可用的主章节。主从复制的数据同步和一致性边界先看第 15 章;Cluster 的分片、槽位、Gossip 和分片级高可用见[第 17 章](/blog/tech/Redis/17.Redis Cluster 分片与高可用/);Go 客户端如何配置 FailoverClient 的基础用法见[第 2 章](/blog/tech/Redis/02.使用 Go 操作 Redis/);故障转移期间连接风暴、重试和观测指标见第 20 章

3. 核心概念

3.1 Sentinel 部署模型

一个典型拓扑如下:

                       +-------------------+
                       |    Go Clients     |
                       | Sentinel-aware    |
                       +---------+---------+
                                 |
                    查询当前主节点 / 故障后重新发现
                                 |
             +-------------------+-------------------+
             |                   |                   |
      +------v------+      +-----v-------+     +-----v-------+
      | Sentinel S1 |      | Sentinel S2 |     | Sentinel S3 |
      +------+------+      +------+------+     +------+------+
             |                    |                   |
             +--------------------+-------------------+
                                  |
                         监控、投票、配置传播
                                  |
                 +----------------+----------------+
                 |                                 |
          +------v------+                   +------v------+
          | Redis Master | ----复制-------> | Redis Replica|
          +-------------+                   +-------------+

生产环境通常至少部署三个 Sentinel,并尽量放在能够独立故障的物理机、虚拟机或可用区中。把三个 Sentinel 都放在同一台机器上,虽然进程数量是三个,但机器故障时它们会一起失效,不能形成真正的高可用。

Sentinel 的配置文件必须可写,因为 Sentinel 会把发现的副本、其他 Sentinel、配置纪元和故障转移后的新主节点写回配置文件。没有配置文件或配置文件不可写时,Sentinel 会拒绝启动。默认监听端口为 26379。 (Redis)


3.2 Sentinel 的四项核心职责

职责作用
监控周期性检查主节点、副本和其他 Sentinel 是否可达、角色是否正确
通知通过日志、Pub/Sub 或通知脚本报告节点异常和故障转移事件
自动故障转移选择合适副本,将其提升为主节点,再让其他副本复制新主节点
服务发现客户端通过主节点逻辑名称查询当前主节点地址

需要注意,Sentinel 通常不位于业务请求的数据通路中。应用不会把每条 GETSET 先发给 Sentinel;应用只在初始化或连接异常后向 Sentinel查询当前主节点,然后直接连接 Redis。


3.3 SDOWN:主观下线

SDOWN,Subjectively Down,主观下线,表示某一个 Sentinel 根据自己的观察,认为某个实例不可用。

Sentinel 会持续向实例发送 PING。如果在 down-after-milliseconds 指定的整个时间窗口内都没有收到可接受的回复,该 Sentinel 就会把实例标记为 SDOWN。

例如:

sentinel down-after-milliseconds mymaster 5000

表示一个 Sentinel 连续约 5 秒没有收到有效回复后,才会认为实例主观下线。

SDOWN 有以下特点:

  • 只代表单个 Sentinel 的本地判断。
  • 主节点、副本和其他 Sentinel 都可能被标记为 SDOWN。
  • 单个 Sentinel 的 SDOWN 不足以触发自动故障转移。
  • 被标记为 SDOWN 的副本不会成为待提升候选节点。

Redis 官方文档列出的有效 PING 回复包括 PONGLOADINGMASTERDOWN;持续收不到有效回复才会形成 SDOWN。 (Redis)


3.4 ODOWN:客观下线

ODOWN,Objectively Down,客观下线,表示足够数量的 Sentinel 都认为某个主节点不可用。

当一个 Sentinel认为主节点 SDOWN 后,它会询问其他 Sentinel:

SENTINEL IS-MASTER-DOWN-BY-ADDR ...

如果至少有 quorum 个 Sentinel 都报告主节点不可达,本地 Sentinel 才会把主节点提升为 ODOWN。

ODOWN 的重要边界是:

  • ODOWN 只应用于主节点。
  • 副本和其他 Sentinel 只有 SDOWN,没有 ODOWN。
  • 进入 ODOWN 才具备启动故障转移流程的前提。
  • ODOWN 本身仍不等于故障转移一定能够执行,因为还需要选出获得足够授权的 Sentinel Leader。 (Redis)

3.5 quorum 与多数派不是同一个概念

这是 Sentinel 面试中最容易答错的地方。

quorum

quorum 用于判断主节点能否进入 ODOWN。

例如:

sentinel monitor mymaster 10.0.0.10 6379 2

最后一个参数 2 表示至少两个 Sentinel 认为主节点不可达,才可将其标记为 ODOWN。

多数派

真正执行故障转移时,某个 Sentinel 还必须获得 Sentinel 集群多数成员的授权。

若 Sentinel 总数为 N,多数派为:

floor(N / 2) + 1

实际启动故障转移所需的授权数,可以理解为:

max(quorum, floor(N / 2) + 1)

示例:

Sentinel 数量quorum形成 ODOWN 所需报告数执行故障转移所需授权数
3222
5223
5333
5444
5555

假设有五个 Sentinel,quorum=2。两个 Sentinel 认为主节点宕机后可以形成 ODOWN,但至少还需要三个 Sentinel 可互相通信,某个候选 Sentinel 才能获得多数派授权并执行故障转移。

因此,quorum=2 并不表示“两个 Sentinel 就足够完成切换”。 (Redis)


3.6 Sentinel Leader 选举

Sentinel 选出的 Leader 不是永久 Leader,也不是新的 Redis 主节点。

它只是:

针对某一个主节点、某一次故障转移过程的协调者。

当主节点进入 ODOWN 后,多个 Sentinel 都可能尝试发起故障转移。它们通过投票决定由哪一个 Sentinel 执行本次操作。

获得授权的 Sentinel 会取得一个新的配置纪元,configuration epoch。配置纪元相当于故障转移配置的版本号:

  • 每次故障转移使用新的纪元。
  • 纪元较大的配置覆盖纪元较小的配置。
  • Sentinel 在网络分区恢复后根据纪元收敛到较新的主节点配置。
  • 一个 Sentinel 为其他候选者投票后,不会立即反复为同一个主节点发起竞争。

这不是完整的 Raft 日志复制,也不应把 Sentinel 描述成通用共识系统。它使用多数派授权和配置纪元来约束故障转移顺序、传播最新拓扑。 (Redis)


3.7 完整故障转移过程

一次正常的自动故障转移大致包括以下步骤。

第一步:主节点进入 SDOWN

每个无法正常联系主节点的 Sentinel 独立形成 SDOWN 判断。

第二步:主节点进入 ODOWN

Sentinel 互相交换主节点下线判断,满足 quorum 后形成 ODOWN。

第三步:选出故障转移 Leader

候选 Sentinel 发起投票,获得足够授权的 Sentinel 成为本次故障转移协调者。

第四步:选择待提升副本

Sentinel 先排除:

  • 当前不可达或处于 SDOWN 的副本。
  • 与旧主断开时间过长的副本。
  • replica-priority0 的副本。
  • 状态或复制信息不足以支持提升的副本。

对剩余副本按以下顺序比较:

  1. replica-priority 更小的优先。
  2. 优先级相同时,复制偏移量更大的优先。
  3. 偏移量也相同时,run ID 字典序更小的优先,用于确定性地打破平局。

replica-priority=0 表示该副本永远不应被 Sentinel 提升。 (Redis)

第五步:提升副本

Leader 向候选副本发送等价于:

REPLICAOF NO ONE

的命令,使其停止复制旧主并切换为主节点。

Sentinel 不能只根据命令发送成功就认定提升完成,还要通过 INFO 等信息确认该节点已报告自己为主节点。

第六步:传播新配置

提升成功后,Leader 发布新的主节点地址和配置纪元。其他 Sentinel 接收更高纪元后更新自己的状态。

第七步:重新配置其他副本

其他副本被依次改为复制新主节点。

parallel-syncs 决定可以同时重新配置多少个副本。例如:

sentinel parallel-syncs mymaster 1

表示一次只安排一个副本切换到新主节点。数值较小会延长整个副本恢复过程,但可以避免所有读副本同时进入重新同步状态。 (Redis)

第八步:旧主恢复后降级

旧主节点重新上线后,Sentinel 会把它重新配置为新主节点的副本。网络分区期间旧主上的分叉数据可能因此被覆盖。

官方源码中的故障转移状态机包括等待开始、选择副本、发送取消复制命令、等待提升、重新配置其他副本和更新最终配置等阶段。 (GitHub)


3.8 客户端如何发现新主节点

支持 Sentinel 的客户端不应把主节点地址当作永久地址,而是持有:

Sentinel 地址列表 + 主节点逻辑名称

客户端发现流程通常是:

  1. 依次或并发尝试连接已知 Sentinel。

  2. 执行:

    SENTINEL GET-MASTER-ADDR-BY-NAME mymaster
  3. 连接返回的 Redis 地址。

  4. 使用 ROLE 等方式确认目标确实是主节点。

  5. 连接中断或收到角色异常后,重新向 Sentinel 查询。

  6. 主节点发生变化时,清理连接池中指向旧主的连接。

Sentinel Pub/Sub 事件可以加速客户端感知,但不能作为唯一正确性来源,因为客户端可能断线并错过通知。重新查询和验证角色才是可靠流程。 (Redis)


3.9 网络分区与脑裂

考虑以下网络分区:

         分区 A                          分区 B

 Client A                             Client B
    |                                    |
 Old Master                         New Master
 Sentinel S1        // 网络中断 //    Sentinel S2
                                     Sentinel S3

分区 B 拥有 Sentinel 多数派,因此可以把一个副本提升为新主节点。

但分区 A 中的旧主节点可能仍然存活,并继续接受 Client A 的写入。此时系统层面出现两个可写节点:

  • 新主节点是 Sentinel 多数派认可的主节点。
  • 旧主节点不知道自己已经失去主节点资格。
  • 网络恢复后,旧主会被降级并复制新主。
  • 旧主在分区期间独有的写入将被丢弃。

因此:

Sentinel 能阻止少数派 Sentinel 发起另一次经过授权的故障转移,但不能主动“关闭”一个网络隔离且仍在运行的旧主节点。

可以配置:

min-replicas-to-write 1
min-replicas-max-lag 10

使主节点在没有至少一个延迟不超过约 10 秒的副本时拒绝写入。这样能够缩小旧主持续接收写入的时间窗口,但代价是网络抖动或副本故障时主动牺牲写可用性。

该配置不能把异步复制变成同步复制,也不能保证零数据丢失。 (Redis)


3.10 RTO 与 RPO

RTO:恢复时间目标

RTO 表示从主节点故障开始,到应用恢复成功读写新主节点所经历的时间。

Sentinel 场景中的 RTO 大致包括:

故障检测时间
+ ODOWN 协商时间
+ Leader 选举时间
+ 副本提升时间
+ 客户端发现和重连时间

因此,RTO 不等于 down-after-milliseconds

RPO:恢复点目标

RPO 表示故障后最多可能丢失多少数据。

Redis 主从复制默认是异步的,旧主已经向客户端返回成功、但尚未复制到最终被提升副本的写入,可能在切换中丢失。

RPO 受以下因素影响:

  • 主从复制延迟。
  • 被提升副本的复制偏移量。
  • 网络分区持续时间。
  • min-replicas-to-write 配置。
  • 客户端是否需要执行额外复制确认。
  • 业务是否允许在旧主分区中继续写入。

3.11 一次主节点宕机的时间线分析

假设:

3 个 Sentinel
quorum = 2
down-after-milliseconds = 5000
parallel-syncs = 1

下面时间仅用于理解,不是性能承诺:

相对时间可能发生的事件
T+0s主节点进程崩溃或网络完全中断
T+0~5sSentinel 持续探测;客户端可能出现超时、EOF 或连接拒绝
T+约5s各 Sentinel 陆续将旧主标记为 SDOWN
T+约5~6s至少两个 Sentinel 同意下线,形成 ODOWN
T+约6sSentinel 进行本次故障转移的 Leader 授权
T+约6~8s选择副本,发送 REPLICAOF NO ONE,等待其报告主角色
T+约7~9sSentinel 开始发布新主地址;客户端重新发现并建立连接
T+后续其他副本按 parallel-syncs 逐步改为复制新主
旧主恢复后Sentinel 将旧主改为新主的副本

实际时间会受到网络延迟、Sentinel 调度、候选副本状态、DNS、客户端超时、连接池和重试策略影响。


4. 命令与 Go 使用方法

4.1 最小 Sentinel 配置

三个 Sentinel 分别使用类似配置,并监听不同机器上的 26379

port 26379

sentinel monitor mymaster 10.0.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

参数含义:

参数含义
mymaster主节点组的逻辑名称,客户端使用该名称发现主节点
2判断 ODOWN 所需的 quorum
down-after-milliseconds单个 Sentinel 形成 SDOWN 前的连续不可达时间
failover-timeout控制故障转移重试、状态超时和副本重新配置等待等流程
parallel-syncs同时切换到新主节点的副本数量

failover-timeout 不是简单的“切换必须在该时间内完成”。它还影响失败后再次尝试故障转移的间隔,以及等待副本重新配置的时间。 (Redis)


4.2 常用 redis-cli 命令

# 查看 Sentinel 自身状态
redis-cli -h 10.0.0.21 -p 26379 INFO sentinel

# 查看指定主节点组
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL MASTER mymaster

# 查看该主节点组的副本
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL REPLICAS mymaster

# 查看监控同一主节点组的其他 Sentinel
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL SENTINELS mymaster

# 查询当前主节点
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL GET-MASTER-ADDR-BY-NAME mymaster

# 检查当前部署是否同时满足 quorum 和多数派要求
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL CKQUORUM mymaster

# 人工触发故障转移,生产环境必须谨慎使用
redis-cli -h 10.0.0.21 -p 26379 \
  SENTINEL FAILOVER mymaster

SENTINEL FAILOVER 可以在旧主仍可达时强制切换,而且不会先要求其他 Sentinel 对主节点故障达成一致,因此应作为受控运维操作,而不是常规健康检查命令。 (Redis)


4.3 使用 go-redis/v9 连接 Sentinel

package main

import (
	"context"
	"errors"
	"log"
	"time"

	"github.com/redis/go-redis/v9"
)

func main() {
	rdb := redis.NewFailoverClient(&redis.FailoverOptions{
		// Sentinel 中配置的逻辑主节点名称。
		MasterName: "mymaster",

		// 至少配置多个 Sentinel 种子地址。
		SentinelAddrs: []string{
			"10.0.0.21:26379",
			"10.0.0.22:26379",
			"10.0.0.23:26379",
		},

		// Sentinel 自身的 ACL 凭据。
		SentinelUsername: "sentinel-app",
		SentinelPassword: "sentinel-secret",

		// Redis 数据节点的 ACL 凭据。
		Username: "redis-app",
		Password: "redis-secret",
		DB:       0,

		DialTimeout:  500 * time.Millisecond,
		ReadTimeout:  time.Second,
		WriteTimeout: time.Second,
		PoolTimeout:  time.Second,

		// 自动重试必须保守设置。
		// 对 INCR、出队、非幂等 Lua 等操作不能依赖无脑重试。
		MaxRetries:      1,
		MinRetryBackoff: 50 * time.Millisecond,
		MaxRetryBackoff: 200 * time.Millisecond,

		ContextTimeoutEnabled: true,
	})

	defer func() {
		if err := rdb.Close(); err != nil {
			log.Printf("close redis client: %v", err)
		}
	}()

	startupCtx, cancel := context.WithTimeout(
		context.Background(),
		2*time.Second,
	)
	defer cancel()

	if err := rdb.Ping(startupCtx).Err(); err != nil {
		log.Fatalf("redis is unavailable: %v", err)
	}

	ctx := context.Background()

	if err := setCache(ctx, rdb, "user:42:name", "Alice"); err != nil {
		log.Printf("set cache failed: %v", err)
	}

	value, found, err := getCache(ctx, rdb, "user:42:name")
	if err != nil {
		log.Printf("get cache failed: %v", err)
		return
	}
	if !found {
		log.Printf("cache miss")
		return
	}

	log.Printf("cache value: %s", value)
}

func setCache(
	parent context.Context,
	rdb *redis.Client,
	key string,
	value string,
) error {
	ctx, cancel := context.WithTimeout(parent, 800*time.Millisecond)
	defer cancel()

	if err := rdb.Set(ctx, key, value, 5*time.Minute).Err(); err != nil {
		return err
	}
	return nil
}

func getCache(
	parent context.Context,
	rdb *redis.Client,
	key string,
) (value string, found bool, err error) {
	ctx, cancel := context.WithTimeout(parent, 800*time.Millisecond)
	defer cancel()

	value, err = rdb.Get(ctx, key).Result()
	switch {
	case err == nil:
		return value, true, nil
	case errors.Is(err, redis.Nil):
		return "", false, nil
	default:
		return "", false, err
	}
}

4.4 代码说明

NewFailoverClient 会使用 MasterName 和 Sentinel 地址发现当前主节点。主节点发生切换、已有连接失效后,客户端会重新查询 Sentinel,而不是继续永久连接旧地址。

需要区分两组认证信息:

  • SentinelUsernameSentinelPassword 用于访问 Sentinel。
  • UsernamePassword 用于访问 Redis 数据节点。

redis.Client 可以安全地被多个 goroutine 共享,不应为每个请求创建新客户端。故障转移期间仍可能出现短暂的超时、EOF、连接关闭或角色错误,业务层必须限制重试次数,并只安全重试满足幂等条件的操作。 (GitHub)

redis.NewSentinelClient 是直接访问某一个 Sentinel 管理接口的客户端;普通业务读写通常应使用 redis.NewFailoverClient


5. 典型业务场景

场景适用性数据量要求一致性要求主要风险可替代方案
单分片业务缓存很适合单个主节点可承载允许短暂丢失或重建切换期间缓存访问失败Redis Cluster、托管 Redis
Session、验证码、临时状态适合中小规模通常允许有限 RPO切换后少量会话或状态丢失数据库、JWT、Cluster
排行榜、计数和实时状态条件适合单节点内存和吞吐可承载需接受异步复制已确认计数可能回退Cluster、数据库事件日志
重要业务状态存储谨慎单分片即可不能接受丢失时不适合分区写丢失、旧主脑裂强一致数据库、共识型 KV
超大数据集或水平扩展不适合超出单节点容量与 Sentinel 无关Sentinel 不分片Redis Cluster
跨地域双活写入不适合任意通常要求冲突处理高延迟、双主冲突Active-Active 产品或可合并数据模型

6. 底层实现

6.1 核心数据结构

Sentinel 内部维护以主节点逻辑名称为索引的字典。每个主节点对象又关联:

  • 当前主节点地址和 run ID。
  • 已发现副本字典。
  • 已发现 Sentinel 字典。
  • SDOWN、ODOWN 时间。
  • quorumparallel-syncsfailover-timeout
  • 当前配置纪元和故障转移纪元。
  • 待提升副本及故障转移状态。
  • 与各实例的异步命令连接和 Pub/Sub 连接。

使用字典的原因是,副本和 Sentinel 都可能动态加入、删除或改变地址,需要高效地按名称、地址或 run ID 查找实例。源码也会在多个主节点组之间共享到同一个 Sentinel 的底层连接,避免监控很多主节点时产生不必要的连接爆炸。 (GitHub)


6.2 自动发现机制

Sentinel 不要求在配置中逐个列出所有副本和其他 Sentinel。

副本主要通过主节点及副本的 INFO 信息发现;其他 Sentinel 通过 Redis Pub/Sub 频道:

__sentinel__:hello

互相发现。

Hello 消息包含 Sentinel 的地址、run ID 和当前主节点配置。收到更高配置纪元的 Sentinel 会更新自己的本地配置。官方实现通常每两秒发布一次 Hello 信息。 (Redis)


6.3 故障转移状态机

从实现角度,故障转移不是一个单命令,而是状态机:

WAIT_START

SELECT_REPLICA

SEND_REPLICAOF_NO_ONE

WAIT_PROMOTION

RECONFIGURE_REPLICAS

UPDATE_CONFIG

状态机设计有三个好处:

  1. 网络命令和角色变化是异步的,不能在一个同步函数里假设全部立即成功。
  2. 每个阶段可以设置超时并记录事件。
  3. Sentinel 进程、目标副本或网络在中间阶段异常时,可以识别失败并重新尝试。

6.4 时间复杂度与空间成本

设一个 Sentinel 监控:

  • M 个主节点组。
  • R 个副本。
  • S 个其他 Sentinel 记录。

主要复杂度可概括为:

操作典型复杂度
按名称查找主节点字典平均 O(1)
周期性遍历被监控实例O(M + R + S)
筛选候选副本O(R)
对候选副本排序通常可视为 O(R log R)
保存拓扑状态O(M + R + S) 空间

Sentinel 通常只监控少量 Redis 拓扑,因此副本排序不是主要性能瓶颈。更值得关注的是 Sentinel 数量过多、跨高延迟网络部署、DNS 阻塞和频繁网络抖动带来的控制面压力。


6.5 配置持久化

Sentinel 会把以下状态写回 sentinel.conf

  • 自己的稳定 ID。
  • 已发现副本和 Sentinel。
  • 当前主节点地址。
  • 配置纪元。
  • 故障转移结果。

这样 Sentinel 重启后可以继续使用最新拓扑,而不是退回最初配置中的旧主节点地址。配置管理系统如果每次部署都强行覆盖 Sentinel 自动重写的配置,可能使节点重启后加载过期状态。 (Redis)


6.6 版本差异

  • Sentinel 2 自 Redis 2.8 起作为稳定实现提供。
  • Redis 2.8.4 起支持运行时添加、删除和修改监控配置。
  • SENTINEL REPLICAS 标注为 Redis 5.0+。
  • Redis 5 起官方术语逐步由 slave 改为 replica,但部分兼容协议和源码符号仍保留旧名称。
  • Redis 6.2+ 支持 Sentinel ACL、SENTINEL CONFIG GET/SETSENTINEL MYID,并可选择启用主机名支持。
  • 主机名支持默认关闭;启用后应保证 DNS 解析稳定,并确认客户端能够处理 Sentinel 返回的主机名。 (Redis)

Redis 8.x 中,Sentinel 的核心模型仍然是单主多副本、故障检测、Leader 授权、配置纪元和客户端服务发现。


7. 高性能、高并发、高可用分析

7.1 高性能

Sentinel 不代理常规业务命令,因此正常状态下几乎不会增加每次 Redis 请求的网络跳数。

主要性能影响来自:

  • Sentinel 对实例执行 PINGINFO 和 Pub/Sub 通信。
  • 故障转移时大量客户端同时重连。
  • 新主提升后,多个副本执行全量或部分重同步。
  • DNS、NAT 或网络延迟拖慢服务发现。
  • parallel-syncs 过大导致多个副本同时失去读服务能力。
  • down-after-milliseconds 过小,导致短暂抖动频繁触发异常判断。

不能为了追求更小 RTO 就盲目把检测时间配置成几百毫秒。真实网络存在调度暂停、瞬时丢包、虚拟机抖动和 GC 停顿,过度敏感会增加误切换。


7.2 高并发

主节点故障时,数百或数千个应用实例可能同时:

  1. 发现连接断开。
  2. 查询 Sentinel。
  3. 创建到新主节点的连接。
  4. 重放失败命令。
  5. 回源数据库或其他依赖。

这可能形成重连风暴和缓存雪崩。

客户端应做到:

  • 使用共享连接池,而不是请求级创建客户端。
  • 设置较短但合理的连接超时和命令超时。
  • 设置有限重试次数及退避。
  • 对重试增加随机抖动。
  • 区分幂等命令和非幂等命令。
  • 对缓存回源使用 singleflight 等请求合并机制。
  • 在故障期间允许部分降级,而不是无限排队。

例如,客户端发送 INCR 后连接断开,无法判断服务端是否已经执行该命令。此时自动重试可能产生重复递增。Sentinel 只负责发现新主节点,不提供端到端“恰好一次”执行保证。


7.3 高可用

一个合理的 Sentinel 部署至少应满足:

  • 三个或五个 Sentinel。
  • Sentinel 位于独立故障域。
  • 至少一个可提升副本。
  • 客户端显式支持 Sentinel。
  • Sentinel 端口互通。
  • Sentinel 和 Redis 的 ACL 配置正确。
  • 配置文件可写。
  • 定期执行 SENTINEL CKQUORUM
  • 定期进行故障演练。
  • 监控复制延迟和候选副本资格。
  • 根据业务选择是否配置 min-replicas-to-write

Sentinel 提高的是故障后的自动恢复能力,而不是把系统变成强一致系统。官方文档明确指出,异步复制下已经确认的写入仍可能在故障中丢失。 (Redis)


8. 常见错误与生产事故

8.1 只有两个 Sentinel

  • 现象: 一台机器故障后,Redis 副本仍然存活,但系统没有自动切换。
  • 根因: 两个 Sentinel 的多数派是两个。如果主节点和其中一个 Sentinel 同机故障,剩余 Sentinel 无法获得多数派授权。
  • 排查方法: 执行 SENTINEL CKQUORUM,检查 num-other-sentinels 和网络连通性。
  • 修复方案: 增加第三个独立 Sentinel。
  • 如何预防: Sentinel 数量使用奇数,并跨独立故障域部署。

8.2 把三个 Sentinel 放在同一台机器

  • 现象: 单个宿主机宕机后,监控和自动故障转移完全失效。
  • 根因: 进程数量增加了,但故障域没有增加。
  • 排查方法: 检查 Sentinel 所在物理机、虚拟机和可用区分布。
  • 修复方案: 将 Sentinel 分散到至少三个独立节点。
  • 如何预防: 架构评审时关注故障域,而不是只统计实例数量。

8.3 客户端硬编码旧主节点地址

  • 现象: Sentinel 已成功切换,但应用持续连接旧主,出现连接失败或 READONLY
  • 根因: 客户端没有使用 Sentinel-aware 连接方式。
  • 排查方法: 对比应用连接目标与 SENTINEL GET-MASTER-ADDR-BY-NAME 返回值。
  • 修复方案: 使用 redis.NewFailoverClient,配置主节点名称和多个 Sentinel 地址。
  • 如何预防: 禁止将当前主节点 IP 作为永久业务配置。

8.4 quorum 满足但故障转移仍未执行

  • 现象: 日志中已经出现 ODOWN,但没有新主节点。
  • 根因: 可以满足 quorum,却无法联系 Sentinel 多数派;或者没有合格副本。
  • 排查方法: 执行 SENTINEL CKQUORUM,检查副本的 SDOWN 状态、优先级、复制链路和复制偏移量。
  • 修复方案: 恢复 Sentinel 网络多数派,保证至少一个合格副本。
  • 如何预防: 同时监控 quorum、可达 Sentinel 数量和可提升副本数量。

8.5 网络分区后出现数据回退

  • 现象: 网络恢复后,部分已经返回成功的数据消失。
  • 根因: 客户端在隔离的旧主上继续写入;多数派侧已经提升新主,旧主恢复后被降级并覆盖。
  • 排查方法: 检查切换时间、客户端连接地址、复制偏移量以及旧主日志。
  • 修复方案: 根据业务配置 min-replicas-to-writemin-replicas-max-lag,对重要写入增加业务幂等与外部记录。
  • 如何预防: 明确 RPO,禁止宣传 Sentinel 为零数据丢失方案。

8.6 down-after-milliseconds 设置过小

  • 现象: 网络轻微抖动时频繁出现 SDOWN、ODOWN 和主从切换。
  • 根因: 故障检测阈值低于基础设施正常抖动范围。
  • 排查方法: 对照 Sentinel 事件、主机负载、网络丢包和延迟监控。
  • 修复方案: 调大阈值,改善网络和宿主机调度。
  • 如何预防: 根据延迟分布和演练结果设置阈值,而不是照搬极端低值。

8.7 Docker、NAT 或 Kubernetes 地址通告错误

  • 现象: Sentinel 能发现节点,但返回的 IP 或端口对客户端不可达。
  • 根因: 实例通告的是容器内部地址,端口映射破坏自动发现。
  • 排查方法: 查看 SENTINEL MASTERREPLICASSENTINELS 中的地址,并从客户端网络实际连接。
  • 修复方案: 正确配置 announce-ipannounce-port,或使用稳定、可路由的网络方案。
  • 如何预防: 在真实网络路径中完成故障演练,不只在容器内部验证。

9. 方案选型与权衡

方案自动故障转移数据分片一致性运维复杂度适用场景
单机 Redis单节点语义开发、可丢失缓存
主从复制,人工切换异步复制有人工值守、低切换频率
Redis + Sentinel异步复制,可能丢失写入单分片高可用缓存或状态服务
Redis Cluster异步复制,非强一致数据量或吞吐超过单节点
托管 Redis 服务通常有依产品而定依产品能力应用侧较低希望降低基础设施运维成本
强一致分布式数据库通常有共识复制不能接受已确认写入丢失

选择 Sentinel 的前提通常是:

数据和流量仍可由单个主节点承载,但业务不能接受主节点故障后依赖人工切换。

一旦核心诉求是水平分片,应该评估 Redis Cluster;一旦核心诉求是已确认写入必须在多故障下保持,应该评估具备共识复制的存储系统,而不是继续堆叠 Sentinel 参数。


10. 高频面试题

问题 1:Redis Sentinel 解决了什么问题?

推荐回答

现场回答:

Sentinel 为非 Cluster 的 Redis 主从部署提供监控、通知、自动故障转移和客户端服务发现。它能在主节点故障后提升副本,并让客户端发现新主,但不负责数据分片,也不能保证零数据丢失。

详细展开:

仅有主从复制时,副本不会自动接管业务。Sentinel 通过多个监控实例共同判断主节点故障,选出本次故障转移的协调者,提升副本、重配其他副本并发布新主地址。

面试官追问

  • Sentinel 是否位于业务请求链路中?
  • Sentinel 是否能替代 Redis Cluster?
  • Sentinel 能否保证已确认写入不丢失?

常见错误回答

“Sentinel 就是 Redis 集群,既能分片又能保证强一致。”

评分点

  • 初级: 知道 Sentinel 能自动切主。
  • 中级: 能说出四项能力和不负责分片。
  • 高级: 能进一步说明异步复制、服务发现和 RTO/RPO 边界。

问题 2:SDOWN 和 ODOWN 有什么区别?

推荐回答

现场回答:

SDOWN 是单个 Sentinel 的主观判断;ODOWN 是达到 quorum 数量的 Sentinel 都认为主节点不可达。SDOWN 可应用于主节点、副本和 Sentinel,ODOWN 只用于主节点,并且只有 ODOWN 才能触发故障转移流程。

详细展开:

Sentinel 连续超过 down-after-milliseconds 没收到有效回复后形成 SDOWN。随后询问其他 Sentinel,达到 quorum 后形成 ODOWN。但真正执行切换还需获得多数派授权。

面试官追问

  • 副本为什么没有 ODOWN?
  • SDOWN 的副本能否被提升?
  • ODOWN 后是否一定会切换?

常见错误回答

“SDOWN 是副本下线,ODOWN 是主节点下线。”

评分点

  • 初级: 能区分单节点判断和集体判断。
  • 中级: 能指出 ODOWN 只用于主节点。
  • 高级: 能继续解释 ODOWN、quorum 和多数派授权之间的关系。

问题 3:quorum 与多数派有什么区别?

推荐回答

现场回答:

quorum 用于确认 ODOWN,多数派用于授权某个 Sentinel 执行故障转移。五个 Sentinel、quorum 为二时,两个 Sentinel 可以判定 ODOWN,但至少三个 Sentinel 可达才能真正执行切换。

详细展开:

所需授权数可以近似理解为 max(quorum, floor(N/2)+1)。Sentinel 不会在只有少数派的网络分区中启动新的自动故障转移。

面试官追问

  • 三个 Sentinel、quorum 为一会怎样?
  • 五个 Sentinel、quorum 为四需要多少授权?
  • quorum 越大是否越安全?

常见错误回答

“quorum 就是集群节点数的一半加一。”

评分点

  • 初级: 知道 quorum 是投票数。
  • 中级: 能区分故障判定和故障转移授权。
  • 高级: 能分析不同 quorum 对误判敏感度和可用性的影响。

问题 4:为什么一般至少部署三个 Sentinel?

推荐回答

现场回答:

因为故障转移需要多数派授权。两个 Sentinel 的多数派仍然是两个,其中一个与主节点同机故障后,剩余 Sentinel 无法切换。三个 Sentinel 可以在失去一个后仍保留两个成员的多数派。

详细展开:

三个 Sentinel 还必须放在独立故障域。三个进程位于同一宿主机不能抵御宿主机故障。

面试官追问

  • 两个 Redis 节点能否部署三个 Sentinel?
  • 五个 Sentinel 相比三个有什么收益?
  • Sentinel 是否必须和 Redis 同机?

常见错误回答

“奇数节点只是为了避免投票平局,两个也可以正常生产使用。”

评分点

  • 初级: 知道推荐三个。
  • 中级: 能算多数派。
  • 高级: 能结合故障域、网络分区和应用节点部署 Sentinel。

问题 5:描述一次 Sentinel 故障转移的完整流程。

推荐回答

现场回答:

主节点先被各 Sentinel 判定 SDOWN,达到 quorum 后进入 ODOWN;随后选出本次故障转移 Leader;Leader筛选副本,向候选副本发送 REPLICAOF NO ONE,确认其成为主节点;再发布新配置,让其他副本复制新主,旧主恢复后也会被降级。

详细展开:

配置传播依赖配置纪元。更高纪元的主节点配置会覆盖旧配置,使网络恢复后的 Sentinel 最终收敛。

面试官追问

  • 什么时候算故障转移成功?
  • 其他副本还没同步完成时算成功吗?
  • Leader 中途宕机会怎样?

常见错误回答

“Sentinel 随机挑一个副本改成主节点,然后结束。”

评分点

  • 初级: 能说出检测、选副本和切换。
  • 中级: 能描述 ODOWN、Leader、重配副本。
  • 高级: 能解释配置纪元和中途失败重试。

问题 6:Sentinel 如何选择要提升的副本?

推荐回答

现场回答:

先排除不可达、断链过久和优先级为零的副本,再按 replica-priority 从小到大、复制偏移量从大到小、run ID 字典序从小到大选择。

详细展开:

偏移量越大通常表示接收的数据越多,但优先级比较在偏移量之前。因此人工设置优先级时必须确认目标副本的网络和复制质量。

面试官追问

  • replica-priority=0 有什么作用?
  • 为什么 run ID 也参与比较?
  • 优先级更高但数据更旧会怎样?

常见错误回答

“永远选择延迟最低或偏移量最大的副本。”

评分点

  • 初级: 知道会从副本中选一个。
  • 中级: 能说出优先级和偏移量。
  • 高级: 能说明候选过滤、比较顺序及错误优先级配置的风险。

问题 7:Sentinel 能完全避免脑裂吗?

推荐回答

现场回答:

不能。Sentinel 可以避免少数派 Sentinel 再发起一次经过授权的故障转移,但隔离的旧主可能继续接受客户端写入。网络恢复后旧主被降级,这部分分叉写入会丢失。

详细展开:

Redis 实例不会因为“其他分区已提升新主”而自动知道自己失去主角色。min-replicas-to-write 可以限制旧主无副本确认时继续写入的时间,但不能提供同步复制保证。

面试官追问

  • 为什么旧主不会自动停止写入?
  • 如何减少分区写丢失?
  • 牺牲可用性是否能换取更小 RPO?

常见错误回答

“有多数派,所以系统任何时候只可能有一个可写主节点。”

评分点

  • 初级: 知道可能脑裂。
  • 中级: 能描述旧主和新主同时可写。
  • 高级: 能区分控制面授权安全与数据面写入行为。

问题 8:min-replicas-to-write 有什么作用和代价?

推荐回答

现场回答:

它让主节点只有在存在足够数量、延迟在限制内的副本时才接受写入,可缩小网络分区中的数据分叉窗口。代价是副本故障或网络抖动时主节点会主动拒绝写入,降低可用性。

详细展开:

它根据副本异步确认和最大允许延迟进行判断,并不是每次写入都等待副本同步提交,因此不能等同于同步复制。

面试官追问

  • 它是否能保证零数据丢失?
  • 缓存业务是否一定要开启?
  • 参数设置过严会发生什么?

常见错误回答

“开启后 Redis 就变成强一致数据库。”

评分点

  • 初级: 知道它与副本数量有关。
  • 中级: 能说出降低数据丢失与降低可用性的权衡。
  • 高级: 能结合网络分区和异步复制解释其边界。

问题 9:Sentinel 场景中的 RTO 和 RPO 分别由什么决定?

推荐回答

现场回答:

RTO 由故障检测、ODOWN 协商、Leader 授权、副本提升以及客户端重连共同决定;RPO 主要由异步复制延迟和最终提升副本的数据进度决定。

详细展开:

降低 down-after-milliseconds 可能缩短检测时间,但也提高误判风险。RPO 不能通过缩短故障检测时间完全解决,因为写入可能在主节点返回成功后尚未复制。

面试官追问

  • down-after-milliseconds=5s 是否意味着 RTO 为 5 秒?
  • 客户端超时如何影响 RTO?
  • 哪些手段可以降低 RPO?

常见错误回答

“RTO 等于 failover-timeout,RPO 永远为零。”

评分点

  • 初级: 知道两个概念的定义。
  • 中级: 能列出主要组成因素。
  • 高级: 能建立完整故障时间线并讨论参数权衡。

问题 10:Go 客户端如何在故障转移后连接新主?

推荐回答

现场回答:

使用 redis.NewFailoverClient,配置 MasterName 和多个 Sentinel 地址。客户端在建立或重建连接时向 Sentinel 查询当前主节点,并清理指向旧主的连接。一个客户端实例可以被多个 goroutine 共享。

详细展开:

还要分别配置 Sentinel ACL 和 Redis ACL,设置连接、读写、命令超时,并限制重试。故障转移期间的非幂等命令不能无脑重放。

面试官追问

  • NewSentinelClientNewFailoverClient 有什么区别?
  • 为什么要配置多个 Sentinel 地址?
  • INCR 超时后是否可以直接重试?

常见错误回答

“应用只需监听 Sentinel Pub/Sub,收到事件后修改全局 IP。”

评分点

  • 初级: 会创建 FailoverClient。
  • 中级: 能处理超时、连接池和 redis.Nil
  • 高级: 能讨论重试不确定性、幂等性和重连风暴。

问题 11:parallel-syncs 应该如何设置?

推荐回答

现场回答:

它控制故障转移后同时重新配置多少个副本。数值大可以更快恢复完整复制拓扑,但多个副本可能同时重新同步而暂时不能稳定提供读服务;数值小恢复较慢,但影响更平滑。

详细展开:

需要结合副本数量、全量同步概率、读流量和磁盘、网络能力设置。常见起点是 1,但不是固定最佳值。

面试官追问

  • 它是否影响新主提升速度?
  • 全量同步时有哪些资源风险?
  • 所有副本都恢复前,故障转移是否已经成功?

常见错误回答

“设置得越大越好,因为可以加速切换。”

评分点

  • 初级: 知道它控制副本同步数量。
  • 中级: 能说明速度与读可用性的权衡。
  • 高级: 能结合全量同步、网络和容量规划分析。

问题 12:Sentinel 与 Redis Cluster 应该如何选择?

推荐回答

现场回答:

数据和流量能由单个主节点承载,只需要单分片自动故障转移时选 Sentinel;需要水平分片、容量超过单节点或希望多主分担写入时选 Redis Cluster。两者都不应被描述成强一致系统。

详细展开:

Sentinel 只有一个业务主节点,数据集完整复制到副本;Cluster 把 Key 分布到 16384 个 Slot,由多个主节点分别负责。Cluster 的客户端还需要处理 Slot 路由和重定向。

面试官追问

  • Sentinel 能否管理多个互不相关的主节点组?
  • 多个 Sentinel 主节点组是否等同于 Cluster?
  • Cluster 是否还需要独立 Sentinel?

常见错误回答

“Sentinel 是小型 Cluster,节点多了自然就能分片。”

评分点

  • 初级: 知道 Cluster 支持分片。
  • 中级: 能比较单主多副本和多分片。
  • 高级: 能结合容量、一致性、客户端和运维复杂度选型。

11. 一分钟面试回答

Redis Sentinel 是非 Cluster Redis 的高可用方案,主要提供监控、通知、自动故障转移和客户端服务发现。单个 Sentinel 连续超过 down-after-milliseconds 无法联系实例时会形成 SDOWN;达到配置的 quorum 后,主节点进入 ODOWN。但 quorum 只负责故障判定,真正执行切换还要有 Sentinel 多数派授权,并选出本次故障转移的 Leader。Leader 会排除不可用副本,然后按副本优先级、复制偏移量和 run ID 选择候选节点,执行 REPLICAOF NO ONE,发布更高配置纪元,再让其他副本复制新主。客户端应使用 NewFailoverClient 根据主节点名称向多个 Sentinel 查询当前主节点。Sentinel 不负责数据分片,也不能保证零数据丢失;异步复制和网络分区下,隔离旧主上的写入仍可能在恢复后丢失。生产环境应至少部署三个跨独立故障域的 Sentinel,并结合 RTO、RPO、min-replicas-to-write、超时、有限重试和故障演练进行设计。


12. 本章总结

Sentinel 的核心价值不是“多运行几个监控进程”,而是建立一个由多个观察者共同驱动的自动故障转移控制面。

必须掌握以下结论:

  1. SDOWN 是单个 Sentinel 的主观判断,ODOWN 是达到 quorum 后对主节点的客观判断。
  2. quorum 用于故障判定,多数派用于授权故障转移,两者不能混为一谈。
  3. Sentinel Leader 是一次故障转移的协调者,不是永久 Leader。
  4. 副本提升不是随机选择,而是经过资格过滤、优先级、偏移量和 run ID 比较。
  5. 客户端必须支持 Sentinel,并在断线后重新发现主节点。
  6. Sentinel 可以提高可用性,但异步复制下仍可能丢失已确认写入。
  7. 多数派能约束新的故障转移,却不能自动关闭网络隔离的旧主。
  8. min-replicas-to-write 是可用性和更小数据分叉窗口之间的权衡。
  9. Sentinel 不能解决分片问题,容量超过单节点时应评估 Redis Cluster。
  10. 没有经过故障演练的高可用配置,不能视为已经验证的高可用系统。

13. 自测清单

  1. 为什么主节点已经 SDOWN,Sentinel 仍可能不执行故障转移?
  2. 三个 Sentinel、quorum=1 时,形成 ODOWN 和执行切换分别需要几个 Sentinel?
  3. 为什么两个 Sentinel 不是可靠的生产部署?
  4. 副本 replica-priority=0 会产生什么效果?
  5. 为什么复制偏移量最大的副本不一定被优先提升?
  6. Sentinel 如何使网络分区恢复后的配置最终收敛?
  7. 为什么旧主在网络分区中仍可能继续接受写入?
  8. min-replicas-to-write 为什么不能提供同步复制保证?
  9. Go 客户端在故障转移期间重试 INCR 有什么风险?
  10. 如何区分 Sentinel 的 RTO 参数调优和 RPO 风险控制?

14. 官方资料

  • Redis 官方文档:High availability with Redis Sentinel。 (Redis)
  • Redis 官方文档:Sentinel client spec。 (Redis)
  • Redis 官方文档:Redis replication。 (Redis)
  • Redis 官方仓库:sentinel.conf。 (GitHub)
  • Redis 官方仓库:src/sentinel.c。 (GitHub)
  • go-redis 官方仓库:sentinel.go / NewFailoverClient。 (GitHub)