Redis Sentinel:故障检测、自动切主与参数调优

一个 Redis Master 宕机,两个 Replica 却都还健康。数据明明有副本,服务却不会自己恢复:Replica 不会主动接管,客户端也不会凭空找到新的写入地址。主从复制的工作到这里已经结束,Sentinel 要处理的,正是故障发生后的这段空白。

没有部署 Sentinel 时,客户端对已宕机 Master 的 GET、SET 和 INCR 请求均不可达;Master 到 Replica A、Replica B 的复制连接已断开,但两个 Replica 仍然存活且没有被晋升

要看懂 Sentinel,可以跟着一次完整的故障转移往下走:多个 Sentinel 如何交换判断并授权其中一个执行切换,新 Master 怎样被选出,整个复制组又如何接受新拓扑。本文先划清职责边界,再沿这条路径拆解原理;第三节转向 Sentinel 本身,说明这些独立进程如何发现同伴并协调行动。随后用本地三 Redis、三 Sentinel 环境观察关键状态,最后落到参数取舍和生产排障。

如果还不熟悉主从复制,可以先读《Redis 主从复制》。本文的机制说明基于 Redis 8.10.0 和源码 commit 5279a8d44818a5ca51e9abb91a9b8ce481d3c88b;实验结果均按实际操作记录,避免把依赖运行环境的现象写成定论。

1. Sentinel 解决什么问题#

主从复制负责把 Master 的数据变化传给 Replicas。它解决的是“故障后有没有副本”,没有回答以下问题:

  • 谁判断 Master 已经不可用;
  • 哪个 Replica 应该接管;
  • 谁修改其余 Replicas 的上游;
  • 客户端到哪里查询新的写入地址。

如果这些动作依赖人工处理,服务恢复时间就取决于告警、判断和操作速度。Sentinel 把这条控制链自动化,但不改变 Redis 异步复制的性质。

这条控制链决定了 Sentinel 的职责范围。Sentinel 是独立于 Redis 数据节点的控制面:一个 Sentinel 进程可以监控多个 Master 复制组,同一个复制组通常由多个 Sentinel 共同管理。

职责 Sentinel 做什么
监控 周期性检查 Master、Replicas 和其他 Sentinels 是否可达
通知 通过事件、日志或通知脚本报告实例状态变化
自动故障转移 在满足故障判断和授权条件后选择 Replica、完成晋升并重配拓扑
配置发现 向客户端返回当前 Master 地址,并报告已知 Replicas

Sentinel 不在 Redis 命令的数据路径上。客户端先向 Sentinel 查询地址,随后直接连接 Redis;Sentinel 不转发读写请求,也不接管已有连接。

明确职责后,还要看它没有接手哪些工作:

问题 Sentinel 是否解决 实际边界
强一致或零写入丢失 Redis 仍是异步复制,已确认但尚未复制的写入可能丢失
透明代理与连接迁移 客户端必须实现发现、地址缓存失效和重连
Replica 读流量准入 Sentinel 能报告节点,业务仍要检查同步、延迟和健康状态
数据分片与水平扩容 Sentinel 管理的是 Master-Replica 复制组,不是 Redis Cluster 槽位
跨地域容灾 故障域、延迟、数据恢复目标和流量调度需要单独设计

Sentinel 的目标是缩短故障发现和拓扑恢复过程。它不会把异步复制变成共识协议,也不能替客户端决定何时恢复业务流量。第 5 节再讨论如何收窄这些风险。

这些职责和边界对应到 Redis 架构中,就是下面的部署关系:

应用客户端先向三个 Sentinel 查询当前 Master 地址,再直接访问 Redis;Sentinel 持续监控一个 Master 和两个 Replicas,Redis 数据只在数据节点之间复制
Redis Sentinel 部署中的控制路径与数据路径

图里有两条不同的路径。数据路径是客户端与 Redis 之间的命令,以及 Master 到 Replicas 的复制流;控制路径是 Sentinel 对节点的监控、Sentinels 之间的信息交换,以及客户端向 Sentinel 发起的地址查询。即使 Sentinel 短时不可用,已经拿到地址的客户端仍可能继续访问 Redis;但它无法在下一次切换时重新发现地址。

三个 Sentinel 的意义不只是多启动两个进程。故障判断需要多个观察者交换意见,故障转移还需要多数派授权。它们若全部位于同一宿主机或同一故障域,进程数并不能抵御该故障域整体失效。

2. Sentinel 原理:监控、选主与拓扑收敛#

Sentinel 的工作主线只有三步:持续监控 Redis 复制组,故障成立后选出新 Master,再把其他节点和客户端引向新拓扑。下图先给出完整过程,后面再分别说明监控、选主和拓扑收敛。

Sentinel 从持续监控开始,依次完成 SDOWN、ODOWN、领导者授权、Replica 选择、晋升、其余 Replica 重配和新配置传播
Sentinel 自动故障转移的判断、授权与拓扑重配

本文实验环境用下面四项配置控制这条流程:

sentinel monitor mymaster redis-1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 30000

mymaster 是监控组名称,redis-1 6379 是初始 Master 地址,末尾的 2 是形成 ODOWN 所需的 quorum。down-after-milliseconds 控制故障判断,另外两个参数用于拓扑切换。配置里不需要逐个列出 Replicas 和其他 Sentinels,它们的发现过程放到第 3 节说明。

2.1 监控:建立视图并判断故障#

Sentinel 通常每 10 秒读取 Master 和 Replicas 的 INFO。它从 Master 的复制信息中发现 Replicas,再继续采集这些节点的角色、上游、复制偏移量和优先级。每个 Sentinel 据此维护自己的监控视图,而不是读取一份由所有 Sentinel 共享的内存状态。

完成发现后,Sentinel 通常每秒向已知 Redis 节点和 peer Sentinels 发送 PING。一个节点在 down-after-milliseconds 内持续没有可接受响应时,当前 Sentinel 将其标记为 SDOWN;对于 Master,它还会通过 SENTINEL is-master-down-by-addr 收集其他 Sentinels 的判断,达到 quorum 后形成 ODOWN。

quorum 控制的是故障判定:至少多少个 Sentinel 同意 Master 下线,才能把它从 SDOWN 推进到 ODOWN。这个值由 sentinel monitor 配置,不会随 Sentinel 数量自动计算。

状态 判断范围 条件
SDOWN 单个 Sentinel 的本地判断 节点持续没有及时回应
ODOWN 仅针对 Master 的故障结论 至少 quorum 个 Sentinel 报告 Master 下线

SDOWN 和 ODOWN 都不会直接改变 Redis 角色。down-after-milliseconds 越小,检测越快,但短时丢包、宿主机暂停和 Redis 主线程阻塞也越容易触发误判。

2.2 选主:先选执行者,再选新 Master#

Sentinel 的“选主”有两层。Master 进入 ODOWN 后,Sentinels 先按故障转移纪元投票,选出负责本轮切换的 Sentinel。在自动故障转移中,候选者的得票数必须同时达到已知 Sentinel 的绝对多数和配置的 quorum,也就是 max(floor(N / 2) + 1, quorum)N 是当前 Master 视图中已知的 Sentinel 总数,包含自身;每个 Sentinel 在同一纪元只投一票。

多数派避免网络分区中的 Sentinel 少数派自行发起切换。quorum 主要控制 ODOWN 的敏感程度,但当它高于多数派时,也会抬高执行者的授权门槛。

三个 Sentinel、quorum 为 12 时,授权都至少需要 2 票。五个 Sentinel、quorum 为 2 时需要 3 票;quorum 为 4 时则需要 4 票。

这里讨论的是自动故障转移。手动执行 SENTINEL FAILOVER 属于强制切换,不需要其他 Sentinels 授权。

执行者确定后,再由 sentinelSelectSlave() 从 Replicas 中选择新的 Redis Master。下面的过滤和排序规则以 Redis 8.10.0 的 src/sentinel.c 为准。

第一环节:排除不合格的 Replica#

/* 摘自 src/sentinel.c:5105 附近,省略无关代码;中文注释为本文补充。 */
mstime_t master_down_time = 0;
if (master->flags & SRI_S_DOWN)
    master_down_time = mstime() - master->s_down_since_time;

/* Replica 最长可断连时间:
 * Master 已处于 SDOWN 的时长 + 10 * down-after-milliseconds。 */
mstime_t max_master_down_time =
    master_down_time + master->down_after_period * 10;

if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue; /* 已被判定下线 */
if (slave->link->disconnected) continue;              /* Sentinel 连接已断开 */
if (mstime() - slave->link->last_avail_time >
    sentinel_ping_period * 5) continue;                /* 默认约 5 秒不可用 */
if (slave->slave_priority == 0) continue;              /* 明确禁止晋升 */

/* Master 已进入 SDOWN 时,只接受默认 5 秒内的 INFO;
 * 其他情况下接受默认 30 秒内的 INFO。 */
if (master->flags & SRI_S_DOWN)
    info_validity_time = sentinel_ping_period * 5;
else
    info_validity_time = sentinel_info_period * 3;

if (mstime() - slave->info_refresh > info_validity_time) continue;
if (slave->master_link_down_time > max_master_down_time) continue;

Master 进入 SDOWN 后,Sentinel 会把 INFO 有效期从默认约 30 秒收紧到约 5 秒。最后一个条件则给 Replica 留出与故障持续时间相关的断连余量,而不是使用固定阈值。

过滤条件中没有 loading。Sentinel 也不会把 INFO 中的 loading 状态解析为候选字段,因此正在 loading 的 Replica 仍可能进入排序并被选中。业务侧不能据此判断节点已经完成加载。

第二环节:对剩余 Replica 排序#

/* 摘自 src/sentinel.c:5077 附近;返回值越小,排序越靠前。 */
if ((*sa)->slave_priority != (*sb)->slave_priority)
    return (*sa)->slave_priority - (*sb)->slave_priority; /* priority 小者优先 */

if ((*sa)->slave_repl_offset > (*sb)->slave_repl_offset)
    return -1; /* offset 大者优先 */
else if ((*sa)->slave_repl_offset < (*sb)->slave_repl_offset)
    return 1;

/* priority 和 offset 都相同时,没有 run ID 的节点排在最后。 */
if (sa_runid == NULL && sb_runid == NULL) return 0;
else if (sa_runid == NULL) return 1;
else if (sb_runid == NULL) return -1;

return strcasecmp(sa_runid, sb_runid); /* run ID 字典序小者优先 */

只有通过第一环节的 Replicas 才会参与排序。比较顺序是 priority、复制偏移量、run ID;priority 代表运维意图,所以它排在复制进度之前。这套规则不能消除异步复制的数据缺口,也不保证故障转移零丢失。

2.3 设置拓扑:晋升、重配与配置传播#

在自动故障转移中,Sentinels 投票选出的是本轮故障转移的执行者,不是新 Master。图中 S1 获得授权后,按 2.2 节的规则选出 Replica A。SLAVEOF 是 Redis 8.10 Sentinel 源码沿用的历史命令名,语义等同于 REPLICAOF;图中带加号的标签均为各进程独立产生的本地事件。

三个 Sentinel 独立健康检查旧 Master 并选出故障转移执行者;S1 选择 Replica A,等待 INFO 确认其成为 Master,用带配置纪元的 HELLO 同步新地址,重配 Replica B;客户端重新查询地址并通过 ROLE 校验角色
箭头表示命令或配置,带加号的色块表示各进程独立产生的本地事件

Redis 8.10.0 的 HELLO 是由逗号分隔的八个字段:

sentinel_ip,sentinel_port,sentinel_runid,current_epoch,
master_name,master_ip,master_port,master_config_epoch

前四个字段标识发送者及其当前纪元,后四个字段描述被监控的 Master 配置。确认 Replica A 晋升后,S1 用它的地址和本轮故障转移纪元构造 HELLO。

HELLO 有两条周期传播路径:

  • Redis 数据节点 Pub/Sub:通过 Master 或 Replica 的 __sentinel__:hello 频道广播配置,同时发现未知的 peer Sentinel。
  • 已知 peer 直连:通过 Sentinel 实现的 fake PUBLISH 点对点刷新配置;它不会真正广播,也不能发现未知 peer。

两条路径并行工作,不是互斥的主备链路。就传播时机而言,晋升确认只是把下一轮 HELLO 提前。

接收方分开处理 HELLO 中的两个纪元:较大的 current_epoch 会推进当前纪元,但只有严格更大的 master_config_epoch 才会覆盖本地 Master 配置。地址变化时产生的 +config-update-from+switch-master 是本地事件,不是配置传播消息。

S1 进入 RECONF_SLAVES 后,sentinelGetCurrentMasterAddress() 已会返回 Replica A,但其内部 Master 条目要到重配阶段收尾才替换。因此,各 Sentinel 可能在短时间内返回不同地址。parallel-syncs 只限制重配并发数;该阶段可因 failover-timeout 收尾,单个 Replica 也可能因内部 slave-reconf-timeout 被按完成处理,所以故障转移完成不代表所有 Replica 都已跟随新 Master。

+switch-master 不是全局收敛信号。客户端应使旧地址缓存失效,按图中流程重新查询并用 ROLE 校验。旧 Master 恢复后,Sentinel 会尝试把它重配为当前 Master 的 Replica,不会自动 failback。

3. Sentinel 是什么:实例发现与协作#

Sentinel 是以 Sentinel 模式运行的独立 redis-server 进程,默认监听 26379,不保存业务数据。一个进程可以监控多个 Master 复制组;每个实例有自己的 myid、连接和本地视图。多个实例常被叫作“Sentinel 集群”,但它们之间没有固定 Master,也没有需要预先登记的成员列表。

sentinel monitor mymaster redis-1 6379 2 只是发现入口:它给出组名、初始 Master 地址和 quorum。Sentinel 连接 Master,通过 INFO 找到 Replicas;随后在每个已知 Redis 节点上发布并订阅 __sentinel__:hello。hello 携带发送者身份、纪元和 Master 配置,收到陌生 runid 后,Sentinel 才把对方加入这个复制组的 peer 视图。

左侧三个 Sentinel 通过每个已知 Redis 节点上的 hello 频道发现同伴,并通过 INFO 从初始 Master 发现 Replicas;右侧三个 peer Sentinel 直连交换 PING、下线报告、投票和纪元,S2 仅作为本轮故障转移的临时执行者,Sentinel 集合没有固定 Master
Sentinel 的 peer 发现与直连协调

Pub/Sub 用于发现 peer 和传播 Master 配置。peer 已知后,周期 PINGSENTINEL is-master-down-by-addr、下线报告和选举投票都走 Sentinel 间直连。每个实例独立保存当前纪元、Master 配置纪元和本轮投票;多数派授权的是一次 failover 的执行者,不是长期存在的 Sentinel Master。

自动发现也有边界。hello 通告的地址必须能被其他 Sentinels 访问,NAT 或端口映射环境要设置 sentinel announce-ipsentinel announce-port;启用 ACL 后,peer 直连使用 sentinel sentinel-usersentinel sentinel-pass。Sentinel 不会因为 peer 长期失联就自动忘记它,永久移除实例时要先停止目标进程,再在其余 Sentinels 上间隔至少 30 秒执行 SENTINEL RESET <master-name>,最后检查成员数与 CKQUORUM

4. 实验:观察 Sentinel 的行为#

本章命令中的 pc 是下面这个 Podman Compose 包装函数:

pc() {
  PODMAN_COMPOSE_WARNING_LOGS=false podman compose \
    -p redis-sentinel-lab -f compose.yaml "$@"
}
三个 Sentinel 组成控制层并共同监控 mymaster,向 Redis 数据层执行周期 PING、INFO 和 hello 发布,在故障时控制 Master 与两个 Replicas 的角色切换;图中同时标注 quorum、检测超时、故障转移超时、复制 backlog 和 diskless sync delay

以下结果实测于 2026-08-04,环境为 Podman 5.5.2、podman-compose 1.5.0 和 Redis 8.10.0。每组实验都先执行 ./run.sh start,删除上一组实验的容器和数据卷,恢复为 redis-1 主、redis-2redis-3 从的固定拓扑。

4.1 观察节点发现与周期监控#

本实验要验证两件事:三个 Sentinel 是否发现了同一个 Master、两个 Replicas 和彼此两个 peers,并且 quorum 与故障转移授权条件均可达;Sentinel 是否会通过周期性的 PINGINFO__sentinel__:hello 发布持续维护这份拓扑视图。

执行命令:

for sentinel in sentinel-1 sentinel-2 sentinel-3; do
  printf '\n# %s\n' "$sentinel"
  printf 'myid='
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL myid
  printf 'master='
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | paste -sd ':' -
  printf 'ckquorum='
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL ckquorum mymaster
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL replicas mymaster | tr -d '\r' | \
    awk '$0 == "name" { replicas++ } END { print "replicas=" replicas + 0 }'
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL sentinels mymaster | tr -d '\r' | \
    awk '$0 == "name" { peers++ } END { print "peer-sentinels=" peers + 0 }'
done
节点发现结果
# sentinel-1
myid=8fdda9f1f59b9679c4bc23846628f8d1ae384df8
master=redis-1:6379
ckquorum=OK 3 usable Sentinels. Quorum and failover authorization can be reached
replicas=2
peer-sentinels=2

# sentinel-2
myid=cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c
master=redis-1:6379
ckquorum=OK 3 usable Sentinels. Quorum and failover authorization can be reached
replicas=2
peer-sentinels=2

# sentinel-3
myid=22215985d8e613ab86d7ebee587babee40d0f171
master=redis-1:6379
ckquorum=OK 3 usable Sentinels. Quorum and failover authorization can be reached
replicas=2
peer-sentinels=2

执行命令:

pc exec -T sentinel-1 redis-cli --raw -p 26379 \
  SENTINEL sentinels mymaster | tr -d '\r' | \
  awk '
    $0 == "name" {
      peer++
      if (peer > 1) print ""
      printf "# peer %d\n", peer
    }
    { print }
  '
Sentinel peer 详情
# peer 1
name
cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c
ip
10.89.1.6
port
26379
runid
cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c
flags
sentinel
link-pending-commands
0
link-refcount
1
last-ping-sent
0
last-ok-ping-reply
82
last-ping-reply
82
down-after-milliseconds
5000
last-hello-message
218
voted-leader
?
voted-leader-epoch
0

# peer 2
name
22215985d8e613ab86d7ebee587babee40d0f171
ip
10.89.1.7
port
26379
runid
22215985d8e613ab86d7ebee587babee40d0f171
flags
sentinel
link-pending-commands
0
link-refcount
1
last-ping-sent
0
last-ok-ping-reply
82
last-ping-reply
82
down-after-milliseconds
5000
last-hello-message
1777
voted-leader
?
voted-leader-epoch
0

执行命令:

printf '# redis-1 MONITOR\n'
pc exec redis-1 redis-cli MONITOR
MONITOR 输出
# redis-1 MONITOR
OK
1785822963.456740 [0 10.89.1.7:42252] "PING"
1785822963.514380 [0 10.89.1.5:41580] "PING"
1785822963.987368 [0 10.89.1.6:47060] "PING"
1785822964.286629 [0 10.89.1.5:41580] "PUBLISH" "__sentinel__:hello" "10.89.1.5,26379,8fdda9f1f59b9679c4bc23846628f8d1ae384df8,0,mymaster,redis-1,6379,0"
1785822964.519722 [0 10.89.1.5:41580] "PING"
1785822964.528096 [0 10.89.1.7:42252] "PING"
1785822964.626970 [0 10.89.1.6:47060] "PUBLISH" "__sentinel__:hello" "10.89.1.6,26379,cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c,0,mymaster,redis-1,6379,0"
1785822964.651873 [0 10.89.1.7:42252] "PUBLISH" "__sentinel__:hello" "10.89.1.7,26379,22215985d8e613ab86d7ebee587babee40d0f171,0,mymaster,redis-1,6379,0"
1785822965.018314 [0 127.0.0.1:39396] "ping"
1785822965.024221 [0 10.89.1.6:47060] "PING"
1785822965.539056 [0 10.89.1.7:42252] "PING"
1785822965.581917 [0 10.89.1.5:41580] "PING"
1785822966.074615 [0 10.89.1.6:47060] "PING"
1785822966.369419 [0 10.89.1.5:41580] "PUBLISH" "__sentinel__:hello" "10.89.1.5,26379,8fdda9f1f59b9679c4bc23846628f8d1ae384df8,0,mymaster,redis-1,6379,0"
1785822966.580222 [0 10.89.1.7:42252] "PING"
1785822966.606710 [0 10.89.1.5:41580] "PING"
1785822966.650339 [0 10.89.1.6:47060] "PUBLISH" "__sentinel__:hello" "10.89.1.6,26379,cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c,0,mymaster,redis-1,6379,0"
1785822966.682308 [0 10.89.1.7:42252] "PUBLISH" "__sentinel__:hello" "10.89.1.7,26379,22215985d8e613ab86d7ebee587babee40d0f171,0,mymaster,redis-1,6379,0"
1785822967.078837 [0 10.89.1.6:47060] "PING"
1785822967.610383 [0 10.89.1.7:42252] "PING"
1785822967.639515 [0 10.89.1.5:41580] "PING"
1785822968.099179 [0 10.89.1.6:47060] "PING"
1785822968.120448 [0 127.0.0.1:59880] "ping"
1785822968.418590 [0 10.89.1.5:41580] "PUBLISH" "__sentinel__:hello" "10.89.1.5,26379,8fdda9f1f59b9679c4bc23846628f8d1ae384df8,0,mymaster,redis-1,6379,0"
1785822968.659030 [0 10.89.1.6:47060] "PUBLISH" "__sentinel__:hello" "10.89.1.6,26379,cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c,0,mymaster,redis-1,6379,0"
1785822968.659443 [0 10.89.1.7:42252] "PING"
1785822968.718104 [0 10.89.1.5:41580] "PING"
1785822968.736650 [0 10.89.1.7:42252] "PUBLISH" "__sentinel__:hello" "10.89.1.7,26379,22215985d8e613ab86d7ebee587babee40d0f171,0,mymaster,redis-1,6379,0"
1785822969.109587 [0 10.89.1.6:47060] "PING"
1785822969.671602 [0 10.89.1.7:42252] "PING"
1785822969.752220 [0 10.89.1.5:41580] "INFO"
1785822969.752246 [0 10.89.1.5:41580] "PING"
1785822969.955942 [0 10.89.1.6:47060] "INFO"
1785822970.018776 [0 10.89.1.7:42252] "INFO"
1785822970.178119 [0 10.89.1.6:47060] "PING"
1785822970.506924 [0 10.89.1.5:41580] "PUBLISH" "__sentinel__:hello" "10.89.1.5,26379,8fdda9f1f59b9679c4bc23846628f8d1ae384df8,0,mymaster,redis-1,6379,0"
1785822970.692314 [0 10.89.1.7:42252] "PING"
1785822970.743760 [0 10.89.1.6:47060] "PUBLISH" "__sentinel__:hello" "10.89.1.6,26379,cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c,0,mymaster,redis-1,6379,0"
1785822970.761169 [0 10.89.1.7:42252] "PUBLISH" "__sentinel__:hello" "10.89.1.7,26379,22215985d8e613ab86d7ebee587babee40d0f171,0,mymaster,redis-1,6379,0"
1785822970.851777 [0 10.89.1.5:41580] "PING"
1785822971.002536 [0 127.0.0.1:59902] "ping"
1785822971.236768 [0 10.89.1.6:47060] "PING"
1785822971.742526 [0 10.89.1.7:42252] "PING"
1785822971.873466 [0 10.89.1.5:41580] "PING"
1785822972.282215 [0 10.89.1.6:47060] "PING"
1785822972.560418 [0 10.89.1.5:41580] "PUBLISH" "__sentinel__:hello" "10.89.1.5,26379,8fdda9f1f59b9679c4bc23846628f8d1ae384df8,0,mymaster,redis-1,6379,0"
1785822972.752865 [0 10.89.1.7:42252] "PING"
1785822972.790844 [0 10.89.1.6:47060] "PUBLISH" "__sentinel__:hello" "10.89.1.6,26379,cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c,0,mymaster,redis-1,6379,0"
1785822972.837558 [0 10.89.1.7:42252] "PUBLISH" "__sentinel__:hello" "10.89.1.7,26379,22215985d8e613ab86d7ebee587babee40d0f171,0,mymaster,redis-1,6379,0"
1785822972.930431 [0 10.89.1.5:41580] "PING"

实验结论:

  • 三个 Sentinel 对 Master、两个 Replicas 和两个 peers 的视图一致。
  • CKQUORUM 确认 quorum 与故障转移授权均可达。
  • peer 记录中的 namerunid 和各实例的 myid 一致,且没有下线或断连标记。
  • 三个 Sentinel 会持续向 Redis 发送 PING、周期执行 INFO,并通过 __sentinel__:hello 发布自身和 Master 配置。

MONITOR 只能观察 Sentinel 与 Redis 之间的命令,不包含 Sentinel 之间的直连协调流量。

4.2 记录一次自动故障转移#

本实验停止当前 Master,持续查询三个 Sentinel,直到它们返回同一个新地址。先动态获取 Master,避免把初始节点写死到停止命令中。

执行命令:

FAILED_MASTER=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
  SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
printf '# 停止当前 Master\n'
printf 'old master: %s\n' "$FAILED_MASTER"
pc stop "$FAILED_MASTER"

printf '\n# 轮询三个 Sentinel 的 Master 视图\n'
NEW_MASTER=''
for round in $(seq 0 45); do
  VIEW_1=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster 2>/dev/null | sed -n '1p')
  VIEW_2=$(pc exec -T sentinel-2 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster 2>/dev/null | sed -n '1p')
  VIEW_3=$(pc exec -T sentinel-3 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster 2>/dev/null | sed -n '1p')
  printf '[round %02d] sentinel-1=%s sentinel-2=%s sentinel-3=%s\n' \
    "$round" "$VIEW_1" "$VIEW_2" "$VIEW_3"

  if [ -n "$VIEW_1" ] && \
     [ "$VIEW_1" = "$VIEW_2" ] && \
     [ "$VIEW_2" = "$VIEW_3" ] && \
     [ "$VIEW_1" != "$FAILED_MASTER" ]; then
    NEW_MASTER=$VIEW_1
    break
  fi
  sleep 1
done

test -n "$NEW_MASTER"
printf 'new master: %s\n' "$NEW_MASTER"

输出:

# 停止当前 Master
old master: redis-1
redis-sentinel-lab_redis-1_1

# 轮询三个 Sentinel 的 Master 视图
[round 00] sentinel-1=redis-1 sentinel-2=redis-1 sentinel-3=redis-1
[round 01] sentinel-1=redis-1 sentinel-2=redis-1 sentinel-3=redis-1
[round 02] sentinel-1=redis-1 sentinel-2=redis-1 sentinel-3=redis-1
[round 03] sentinel-1=redis-1 sentinel-2=redis-1 sentinel-3=redis-1
[round 04] sentinel-1=redis-2 sentinel-2=redis-2 sentinel-3=redis-2
new master: redis-2

可以看到在 round4(~5s)的时候就完成了新的选主,这个时间也符合实验环境 down-after-milliseconds mymaster 5000 的设置,这时候检查全部 Sentinel 中的相关日志:

执行命令:

for sentinel in sentinel-1 sentinel-2 sentinel-3; do
  printf '\n# %s\n' "$sentinel"
  pc logs "$sentinel" 2>&1 | \
    grep -E '\+(sdown|odown|new-epoch|try-failover|vote-for-leader|elected-leader|selected-slave|promoted-slave|switch-master)'
done
故障转移事件日志
# sentinel-1
1:X 04 Aug 2026 08:28:44.398 # +sdown master mymaster redis-1 6379
1:X 04 Aug 2026 08:28:44.506 # +new-epoch 1
1:X 04 Aug 2026 08:28:44.506 # +vote-for-leader cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c 1
1:X 04 Aug 2026 08:28:45.477 # +odown master mymaster redis-1 6379 #quorum 3/2
1:X 04 Aug 2026 08:28:46.711 # +switch-master mymaster redis-1 6379 redis-2 6379

# sentinel-2
1:X 04 Aug 2026 08:28:44.441 # +sdown master mymaster redis-1 6379
1:X 04 Aug 2026 08:28:44.499 # +odown master mymaster redis-1 6379 #quorum 2/2
1:X 04 Aug 2026 08:28:44.499 # +new-epoch 1
1:X 04 Aug 2026 08:28:44.499 # +try-failover master mymaster redis-1 6379
1:X 04 Aug 2026 08:28:44.502 # +vote-for-leader cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c 1
1:X 04 Aug 2026 08:28:44.566 # +elected-leader master mymaster redis-1 6379
1:X 04 Aug 2026 08:28:44.645 # +selected-slave slave redis-2:6379 redis-2 6379 @ mymaster redis-1 6379
1:X 04 Aug 2026 08:28:45.560 # +promoted-slave slave redis-2:6379 redis-2 6379 @ mymaster redis-1 6379
1:X 04 Aug 2026 08:28:46.648 # +switch-master mymaster redis-1 6379 redis-2 6379

# sentinel-3
1:X 04 Aug 2026 08:28:44.478 # +sdown master mymaster redis-1 6379
1:X 04 Aug 2026 08:28:44.506 # +new-epoch 1
1:X 04 Aug 2026 08:28:44.506 # +vote-for-leader cab4401ab1d00db3db2ccd0adcfcd5eecaadad0c 1
1:X 04 Aug 2026 08:28:44.534 # +odown master mymaster redis-1 6379 #quorum 3/2
1:X 04 Aug 2026 08:28:46.711 # +switch-master mymaster redis-1 6379 redis-2 6379

可以看到,sentinel-2 是本轮执行者,它选中并晋升了 redis-2

最后启动旧 Master,等待 Sentinel 把它重配为新 Master 的 Replica。

执行命令:

printf '# 启动旧 Master\n'
pc start "$FAILED_MASTER"

printf '\n# 等待旧 Master 恢复为 Replica\n'
for attempt in $(seq 1 60); do
  if pc exec -T "$FAILED_MASTER" redis-cli --raw INFO replication \
    2>/dev/null | tr -d '\r' | \
    awk -F: '
      $1 == "role" { role=$2 }
      $1 == "master_link_status" { link=$2 }
      END { exit !(role == "slave" && link == "up") }
    '
  then
    printf 'recovered after %ss\n' "$attempt"
    break
  fi
  sleep 1
done

printf '\n# 旧 Master 的复制状态\n'
pc exec -T "$FAILED_MASTER" redis-cli --raw INFO replication | \
  sed -n '/^role:/p;/^master_host:/p;/^master_link_status:/p'

printf '\n# 三个 Sentinel 的 Master 视图\n'
for sentinel in sentinel-1 sentinel-2 sentinel-3; do
  printf '%s -> ' "$sentinel"
  pc exec -T "$sentinel" redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | paste -sd ':' -
done

输出:

# 启动旧 Master
redis-sentinel-lab_redis-1_1

# 等待旧 Master 恢复为 Replica
recovered after 33s

# 旧 Master 的复制状态
role:slave
master_host:redis-2
master_link_status:up

# 三个 Sentinel 的 Master 视图
sentinel-1 -> redis-2:6379
sentinel-2 -> redis-2:6379
sentinel-3 -> redis-2:6379

实验结论:

  • redis-1 停止后,三个 Sentinel 先后形成 SDOWN 和 ODOWN。
  • sentinel-2 获得选票,选中 redis-2 并完成晋升。
  • 第 5 轮查询时,三个 Sentinel 的地址视图已经收敛到 redis-2,新 Master 读写正常。
  • 旧 Master 启动后约 33 秒以 Replica 身份恢复。

4.3 观察恢复中 Replica 的 Sentinel 状态#

Sentinel 能发现一个 Replica,不代表这个节点已经完成同步。本实验利用 64 KiB backlog 和 30 秒 diskless sync delay 拉长恢复窗口。

先停止当前 Master,等待三个 Sentinels 对新 Master 达成一致:

执行命令:

RECOVERING_NODE=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
  SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
printf '# 停止当前 Master: %s\n' "$RECOVERING_NODE"
pc stop "$RECOVERING_NODE"

printf '\n# 等待三个 Sentinel 的 Master 视图收敛\n'
for attempt in $(seq 0 45); do
  MASTER_1=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | paste -sd ':' -)
  MASTER_2=$(pc exec -T sentinel-2 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | paste -sd ':' -)
  MASTER_3=$(pc exec -T sentinel-3 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | paste -sd ':' -)
  if [ "$MASTER_1" = "$MASTER_2" ] && \
     [ "$MASTER_2" = "$MASTER_3" ] && \
     [ "$MASTER_1" != "$RECOVERING_NODE:6379" ]; then
    printf 'consensus reached at round %s\n' "$attempt"
    break
  fi
  sleep 1
done

NEW_MASTER=${MASTER_1%:6379}
printf 'new master: %s\n' "$NEW_MASTER"

输出:

# 停止当前 Master: redis-1
redis-sentinel-lab_redis-1_1

# 等待三个 Sentinel 的 Master 视图收敛
consensus reached at round 4
new master: redis-3

旧节点仍停止时,向新 Master 写入约 10 MB 数据,使偏移差超过实验 backlog:

执行命令:

printf '# 写入实验键\n'
pc exec -T sentinel-1 redis-cli -h "$NEW_MASTER" \
  SET lab:recovery-window ready
printf '\n# 写入约 10 MB 数据\n'
pc exec -T sentinel-1 redis-benchmark -h "$NEW_MASTER" \
  -t set -n 10000 -d 1024 -r 1000000 -q

输出:

# 写入实验键
OK

# 写入约 10 MB 数据
SET: 175438.59 requests per second, p50=0.207 msec

在终端 A 运行观察循环。它只抽取恢复节点在 Sentinel 中的 flags 和复制链路,同时查询该 Redis 进程自己的复制状态:

执行命令:

RECOVERING_NODE=redis-1
NEW_MASTER=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
  SENTINEL get-master-addr-by-name mymaster | sed -n '1p')

while :; do
  date '+%H:%M:%S'
  pc exec -T sentinel-1 redis-cli --raw -p 26379 \
    SENTINEL replicas mymaster 2>/dev/null | tr -d '\r' | \
    awk -v target="$RECOVERING_NODE" '
      function emit() {
        if (name ~ ("^" target ":") || ip == target) {
          printf "name=%s ip=%s flags=%s link=%s link-down-ms=%s\n", \
            name, ip, flags, link, down
        }
      }
      $0 == "name" {
        emit()
        name=""; ip=""; flags=""; link=""; down=""
        getline; name=$0; next
      }
      $0 == "ip" { getline; ip=$0; next }
      $0 == "flags" { getline; flags=$0; next }
      $0 == "master-link-status" { getline; link=$0; next }
      $0 == "master-link-down-time" { getline; down=$0; next }
      END { emit() }
    '

  pc exec -T "$RECOVERING_NODE" redis-cli --raw INFO replication \
    2>/dev/null | \
    sed -n '/^role:/p;/^master_host:/p;/^master_link_status:/p;/^master_sync_in_progress:/p'
  sleep 1
done

在终端 B 启动旧节点:

执行命令:

pc start redis-1

输出中每秒变化的 link-down 时长和重复状态已经删去,只保留状态转换:

输出:

# 恢复节点状态转换
[00s] sentinel: flags=slave,disconnected link=err link-down-ms=0 | redis: role=master
[01s] sentinel: flags=slave link=err link-down-ms=0 | redis: role=master
[11s] sentinel: flags=slave link=err link-down-ms=-1000 | redis: role=slave master=redis-3 link=down sync=0
[42s] sentinel: flags=slave link=ok link-down-ms=0 | redis: role=slave master=redis-3 link=up sync=0

等恢复节点显示 master_link_status:up 后,从恢复节点读取实验键,再从新 Master 的日志确认实际同步路径。

执行命令:

printf '# 从恢复后的 Replica 读取实验键\n'
pc exec -T "$RECOVERING_NODE" redis-cli GET lab:recovery-window
printf '\n# 新 Master 的同步日志\n'
pc logs "$NEW_MASTER" 2>&1 | \
  grep -E 'Full resync|BGSAVE|diskless|Synchronization with replica'

输出:

# 从恢复后的 Replica 读取实验键
ready

# 新 Master 的同步日志
1:M 04 Aug 2026 08:36:01.434 * Full resync requested by replica redis-1:6379 (rdb-channel)
1:M 04 Aug 2026 08:36:01.434 * Delay next BGSAVE for diskless SYNC
1:M 04 Aug 2026 08:36:31.791 * Starting BGSAVE for SYNC with target: replicas sockets (rdb-channel)
54:C 04 Aug 2026 08:36:31.835 * BGSAVE done, 9962 keys saved, 0 keys skipped, 10399539 bytes written.
1:M 04 Aug 2026 08:36:31.859 * Synchronization with replica redis-1:6379 succeeded

实验结论:

  • 恢复节点尚未启动时,Sentinel 已经在 Replicas 列表中保留了它,状态是 slave,disconnected、链路是 err
  • 节点启动后先以 role=master 运行;约 11 秒后被重配为 slave,但直连复制链路仍为 down。这时 master_sync_in_progress=0,同样不能说明节点可读。
  • Master 日志记录了 full resync,其中约 30 秒花在 diskless sync 等待。约 42 秒时,Redis 报告 master_link_status=up,Sentinel 才报告 link=ok,实验键也能从该 Replica 读出。
  • 节点出现在 SENTINEL REPLICAS 中,不代表它已经完成数据恢复并可对外提供服务。

本次实验中,本地 10 MB 数据的实际传输在一次轮询间隔内完成,因此没有采样到 master_sync_in_progress=1

4.4 执行一次计划内故障转移#

计划切换不需要先制造 ODOWN。Redis 8.10 的 SENTINEL FAILOVER 会进入强制故障转移路径,跳过 ODOWN 判断和正常领导者选举授权。操作前执行 CKQUORUM 是运维安全门,用来暴露 Sentinel 数量或通信异常,不是该命令执行的前置条件。还要记录候选 Replicas 的复制状态。实验环境没有持续写入者,下面的检查不能替代生产中的写入排空:

执行命令:

printf '# 检查 Sentinel 可用性\n'
pc exec -T sentinel-1 redis-cli -p 26379 SENTINEL CKQUORUM mymaster

printf '\n# 查询当前 Master\n'
OLD_MASTER=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
  SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
printf 'old master: %s\n' "$OLD_MASTER"

for node in redis-1 redis-2 redis-3; do
  printf '\n# %s: INFO replication\n' "$node"
  pc exec -T "$node" redis-cli --raw INFO replication | \
    sed -n '/^role:/p;/^master_host:/p;/^master_link_status:/p;/^master_repl_offset:/p;/^slave_repl_offset:/p'
done
切换前复制状态
# 检查 Sentinel 可用性
OK 3 usable Sentinels. Quorum and failover authorization can be reached

# 查询当前 Master
old master: redis-1

# redis-1: INFO replication
role:master
master_repl_offset:9062

# redis-2: INFO replication
role:slave
master_host:redis-1
master_link_status:up
slave_repl_offset:9062
master_repl_offset:9062

# redis-3: INFO replication
role:slave
master_host:redis-1
master_link_status:up
slave_repl_offset:9062
master_repl_offset:9062

通过任一 Sentinel 发起受控切换,再等待三个地址视图一致:

执行命令:

printf '# 发起计划切换\n'
pc exec -T sentinel-1 redis-cli -p 26379 SENTINEL FAILOVER mymaster

printf '\n# 等待三个 Sentinel 的 Master 视图收敛\n'
for round in $(seq 0 45); do
  MASTER_1=$(pc exec -T sentinel-1 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
  MASTER_2=$(pc exec -T sentinel-2 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
  MASTER_3=$(pc exec -T sentinel-3 redis-cli --raw -p 26379 \
    SENTINEL get-master-addr-by-name mymaster | sed -n '1p')
  printf '[round %02d] sentinel-1=%s sentinel-2=%s sentinel-3=%s\n' \
    "$round" "$MASTER_1" "$MASTER_2" "$MASTER_3"
  if [ "$MASTER_1" = "$MASTER_2" ] && \
     [ "$MASTER_2" = "$MASTER_3" ] && \
     [ "$MASTER_1" != "$OLD_MASTER" ]; then
    NEW_MASTER=$MASTER_1
    break
  fi
  sleep 1
done

printf 'master moved: %s -> %s\n' "$OLD_MASTER" "$NEW_MASTER"
printf '\n# 验证新 Master 读写\n'
pc exec -T sentinel-1 redis-cli -h "$NEW_MASTER" \
  SET lab:planned-failover ok
pc exec -T sentinel-1 redis-cli -h "$NEW_MASTER" \
  GET lab:planned-failover

输出:

# 发起计划切换
OK

# 等待三个 Sentinel 的 Master 视图收敛
[round 00] sentinel-1=redis-1 sentinel-2=redis-1 sentinel-3=redis-1
[round 01] sentinel-1=redis-3 sentinel-2=redis-3 sentinel-3=redis-3
master moved: redis-1 -> redis-3

# 验证新 Master 读写
OK
ok

旧 Master 没有停止,但 Sentinel 仍会把它重配为 Replica。等待复制链路恢复并提取事件:

执行命令:

printf '# 等待旧 Master 恢复为 Replica\n'
for attempt in $(seq 1 60); do
  if pc exec -T "$OLD_MASTER" redis-cli --raw INFO replication \
    2>/dev/null | tr -d '\r' | \
    awk -F: '
      $1 == "role" { role=$2 }
      $1 == "master_link_status" { link=$2 }
      END { exit !(role == "slave" && link == "up") }
    '
  then
    printf 'old master recovered after %ss\n' "$attempt"
    break
  fi
  sleep 1
done

printf '\n# 旧 Master 的复制状态\n'
pc exec -T "$OLD_MASTER" redis-cli --raw INFO replication | \
  sed -n '/^role:/p;/^master_host:/p;/^master_link_status:/p'

printf '\n# 三个 Redis 的最终角色\n'
for node in redis-1 redis-2 redis-3; do
  printf '%s: ' "$node"
  pc exec -T "$node" redis-cli --raw ROLE | sed -n '1p'
done

printf '\n# Sentinel 故障转移事件\n'
for sentinel in sentinel-1 sentinel-2 sentinel-3; do
  printf '\n# %s\n' "$sentinel"
  pc logs "$sentinel" 2>&1 | \
    grep -E '\+(sdown|odown|new-epoch|try-failover|elected-leader|selected-slave|promoted-slave|switch-master)'
done
计划切换事件日志
# 等待旧 Master 恢复为 Replica
old master recovered after 8s

# 旧 Master 的复制状态
role:slave
master_host:redis-3
master_link_status:up

# 三个 Redis 的最终角色
redis-1: slave
redis-2: slave
redis-3: master

# Sentinel 故障转移事件

# sentinel-1
1:X 04 Aug 2026 08:37:57.589 # +new-epoch 1
1:X 04 Aug 2026 08:37:57.589 # +try-failover master mymaster redis-1 6379
1:X 04 Aug 2026 08:37:57.620 # +elected-leader master mymaster redis-1 6379
1:X 04 Aug 2026 08:37:57.722 # +selected-slave slave redis-3:6379 redis-3 6379 @ mymaster redis-1 6379
1:X 04 Aug 2026 08:37:58.662 # +promoted-slave slave redis-3:6379 redis-3 6379 @ mymaster redis-1 6379
1:X 04 Aug 2026 08:38:10.099 # +switch-master mymaster redis-1 6379 redis-3 6379

# sentinel-2
1:X 04 Aug 2026 08:37:57.919 # +new-epoch 1
1:X 04 Aug 2026 08:37:58.723 # +switch-master mymaster redis-1 6379 redis-3 6379

# sentinel-3
1:X 04 Aug 2026 08:37:57.919 # +new-epoch 1
1:X 04 Aug 2026 08:37:58.723 # +switch-master mymaster redis-1 6379 redis-3 6379

实验结论:

  • 切换前三个 Redis 的复制偏移量都是 9062,两个 Replica 链路也是 up
  • SENTINEL FAILOVER 直接返回 OK,日志中没有 SDOWN 或 ODOWN;sentinel-1 选中并晋升了 redis-3
  • 下一轮查询时,三个 Sentinel 都已经返回 redis-3
  • 旧 Master 始终没有停机,约 8 秒后以 Replica 身份恢复复制。

日志仍出现历史命名的 +elected-leader,但它不能证明这次强制切换走了自动路径的多数派授权。各 Sentinel 的 +switch-master 时间也不完全一致,不能把某一个进程的日志时间直接当成客户端地址切换时间。生产操作仍需要排空写入、确认候选进度,并单独验证客户端连接池的刷新行为。

5. 影响 Sentinel 行为的参数#

完整配置请先看 Redis 官方的 Redis 8.10.0 sentinel.conf;参数细节和版本变化以 Redis Sentinel 官方文档 为准。

本节只保留会直接改变故障判定、故障转移或新 Master 选择结果的参数。认证、目录、脚本、DNS、NAT 和 TLS 等部署配置不在这里展开。表中的重要程度是本文针对生产环境的判断,不是 Redis 官方评级;本节没有“低”级参数。

参数 重要程度 Redis 8.10.0 默认值 / 推荐起点 作用 生产注意事项
sentinel monitor <name> <host> <port> <quorum> 无默认;三个 Sentinel 推荐 quorum 2 创建监控组,并规定形成 ODOWN 所需的同意数 quorum 先控制 ODOWN;如果高于多数派,也会抬高执行者的授权门槛
sentinel down-after-milliseconds <name> <ms> 默认 30000 ms;稳定同地域网络可从 10000 ms 起测 节点持续没有可接受响应达到该时长后进入 SDOWN 过小容易把网络抖动、宿主机暂停或 Redis 主线程阻塞当成故障,过大则延迟切换
sentinel master-reboot-down-after-period <name> <ms> 默认及推荐起点 0 Master 重启后,Sentinel 接受 -LOADING 响应的最长时间 0 表示持续接受 -LOADING,不会因此发起切换;设置非零值前要先测出正常加载耗时
sentinel failover-timeout <name> <ms> 默认及推荐起点 180000 ms 控制故障转移重试、晋升超时判断和 Replica 重配期限 它不是一次切换的简单总时限;直接调小可能让仍在推进的故障转移重复进入新轮次
sentinel parallel-syncs <name> <count> 默认及推荐起点 1 新 Master 产生后,同时重配的 Replicas 数量 调高可以加快拓扑恢复,但会让更多只读节点同时不可用,并集中消耗新 Master 的资源
replica-priority 默认 100;按候选能力设置正整数 Sentinel 对候选 Replicas 排序时,数值较小者优先 该参数写在 redis.conf0 表示永不晋升,也会减少可用候选

quorum 先用于把 Master 从 SDOWN 推进到 ODOWN。自动故障转移选举执行者时,授权门槛是 max(floor(N / 2) + 1, quorum),其中 N 是当前 Master 视图中已知的 Sentinel 总数,包含自身。因此,把三个 Sentinel 的 quorum 从 2 改成 1,仍然至少需要 2 票。手动执行 SENTINEL FAILOVER 不受这个授权条件限制。

down-after-milliseconds 应覆盖正常运行时的尾部延迟、短时丢包和最长宿主机暂停。第四节为了缩短实验时间使用了 5 秒,生产环境不要直接照抄。

failover-timeout 在状态机的多个阶段复用。没有实际事件日志证明重试间隔过长时,保留默认值比按目标 RTO 直接压缩更稳妥。

parallel-syncs 取决于切换期间要保留多少只读容量,以及一次全量同步对新 Master 的影响。有两个 Replicas 且业务依赖副本读取时,建议先保留 1

Sentinel 会先排除不健康的 Replicas,再按 priority、复制偏移量和 run ID 排序。priority 排在复制进度之前:如果所有节点都具备同等接管能力,保持相同值即可;只有明确希望某个节点优先或禁止晋升时才区分配置。

对于一个 Master、两个 Replicas、三个 Sentinel 的同地域部署,可以先从下面这组配置开始演练:

sentinel monitor mymaster 10.0.1.10 6379 2
sentinel down-after-milliseconds mymaster 10000
sentinel master-reboot-down-after-period mymaster 0
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

可能参与晋升的 Redis 节点先保持默认优先级:

replica-priority 100

这组值只是演练起点。上线前至少验证一次 Master 故障:记录 SDOWN、ODOWN、选举、晋升和 Replica 重配的耗时,确认被选中的 Replica 符合预期,再根据观测结果调整参数。

6. 总结#

Sentinel 是 Redis 主从复制之外的分布式控制面。 每个 Sentinel 独立监控拓扑,先记录 SDOWN,再通过 quorum 形成 ODOWN。进入自动故障转移后,Sentinels 按纪元授权一个执行者,由它选择并晋升 Replica、重配其余节点,客户端最后重新发现 Master。

Redis 将故障判断分散给多个 Sentinel,只把一次切换交给一个执行者。 单个进程无法可靠区分节点宕机和网络分区,因此 ODOWN 需要多个观察者;自动故障转移还要取得多数派授权,同一纪元只由一个 Sentinel 协调。数据复制继续留在 Redis 主从链路,Sentinel 不进入数据路径。

Sentinel 的优点是能以较低成本为现有主从复制补上自动故障转移。 它无需代理数据流,可以按健康状态、优先级和复制进度选择候选,并让旧 Master 作为 Replica 回归。多个 Sentinel 跨故障域部署后,也避免了单个监控进程成为控制面单点。

Sentinel 优先恢复可用性,因此保留了异步复制的一致性风险。 未同步写入仍可能丢失,分区中的旧 Master 也可能继续接收写入。实际恢复依赖 Sentinel 部署、地址通告、配置持久化和客户端重连;它不负责读副本准入,也不解决分片或跨地域强一致。

参考资料#

访问量 访客数