🔥 本文专栏:Redis
🌸作者主页:努力努力再努力wz



💪 今日博客励志语录:
你可以暂时没有结果,但不能长期没有积累。
思维导图
text
Redis 单节点
↓
单节点故障会导致服务不可用
↓
主从复制
↓
Master 负责写入并产生复制流
Replica 跟随 Master
↓
replid + offset 标识复制历史中的位置
↓
backlog 保存一段复制流历史
↓
断线后 Replica 自动重连
↓
能接上 backlog → Partial Resync
接不上 → Full Resync
↓
但是:Master 真正挂掉以后
Replica 只会重连旧 Master,不会自己选新 Master
↓
需要高可用控制层
↓
Redis Sentinel
↓
监控 Master / Replica
自动发现 Replica / 其他 Sentinel
↓
SDOWN:单个 Sentinel 的主观下线判断
↓
quorum 达标
↓
ODOWN:多个 Sentinel 确认 Master 不可用
↓
进入新的 epoch
↓
Sentinel Leader Election
↓
多数派授权
↓
唯一 Leader 获得 Failover 执行权
↓
筛选新的 Master 候选 Replica
↓
失联时间资格过滤
↓
replica-priority
↓
replication offset
↓
runid
↓
选出新 Master
↓
REPLICAOF NO ONE
↓
其余 Replica 改为复制新 Master
↓
新配置向其他 Sentinel 传播
↓
旧 Master 恢复
↓
被 Sentinel 降级为新 Master 的 Replica
↓
PSYNC 重新加入当前复制拓扑
引入:主从复制已经有了,为什么还需要 Sentinel
在前面对 Redis 主从复制的学习中,我们已经认识到,Redis 可以通过 Master 和 Replica 构造一套复制拓扑。
最简单的结构如下:
text
Master
/ \
↓ ↓
Replica1 Replica2
Master 接收写请求,并将这些写操作形成复制流发送给下游 Replica。Replica 则不断跟随 Master,使自己的数据状态尽量接近 Master。
如果只是某个 Replica 和 Master 之间的复制连接暂时断开,其实问题并不大,因为 Replica 会不断尝试重新连接原来的上游,然后通过 PSYNC 尝试恢复复制。
真正的问题在于:
如果挂掉的是 Master 本身,Replica 虽然拥有数据副本,但是它并不会因为 Master 挂掉就自动把自己提升为新的 Master。
此时如果没有额外机制,就只能人工完成:
text
确认 Master 故障
↓
挑选一个 Replica
↓
将 Replica 提升为 Master
↓
让其他 Replica 改为复制新的 Master
↓
让客户端找到新的 Master
这一整套操作如果依赖人工完成,不但恢复速度慢,而且非常容易因为判断错误或者操作错误导致更严重的问题。
因此,Redis 在普通主从复制之上,又增加了一层专门负责高可用控制的机制:
text
Redis Sentinel
要真正理解 Sentinel,不能一上来就记:
text
SDOWN
ODOWN
quorum
majority
epoch
Leader
而应该先从主从复制本身开始,把 Sentinel 为什么会出现以及它接管了哪些事情串起来。
一、先重新建立 Redis 主从复制的底层模型
1. Master / Replica 与 Upstream / Downstream 是两套不同概念
在 Redis 复制中,经常会看到两组词:
text
Master / Replica
以及:
text
Upstream / Downstream
这两组概念不能完全混为一谈。
Master / Replica 描述的是 Redis 节点当前所承担的角色。
而 Upstream / Downstream 描述的是某一条复制连接中的方向关系。
例如:
text
A Master
↓
B Replica
↓
C Replica
对于 A 和 B:
text
A = B 的 upstream
B = A 的 downstream
对于 B 和 C:
text
B = C 的 upstream
C = B 的 downstream
这里 B 虽然自身角色是 Replica,但是它仍然可以成为 C 的 upstream。
因此更准确的理解是:
Master / Replica 是节点角色,upstream / downstream 是复制链路上的相对关系。
2. Redis 主从复制本质上是在复制一条"数据变化历史"
假设客户端向 Master 连续写入:
text
SET a 1
SET b 2
INCR count
DEL temp
Master 并不是简单地告诉 Replica:
text
"我现在内存里有哪些 key。"
而是不断向 Replica 发送一条复制流,使 Replica 重放 Master 上发生的数据变化。
因此可以把整个复制过程理解为:
text
客户端写请求
↓
Master 修改自身数据
↓
产生 replication stream
↓
发送给 Replica
↓
Replica 重放这些变化
↓
逐渐达到与 Master 一致的状态
Redis 默认采用异步复制。
也就是说,正常情况下并不是:
text
Master 发送一批数据
↓
停下来等待 Replica ACK
↓
再发送下一批
而是 Master 可以继续处理请求、继续产生复制流,而 Replica 会异步向 Master 汇报自己已经处理到了哪个位置。
所以 ACK 更准确的作用是:
text
"我已经处理到 replication offset = X 了。"
而不是每次都作为 Master 继续发送数据之前必须等待的确认信号。
3. replid + offset:给复制历史建立坐标系
如果 Redis 只是不断发送字节流,那么 Replica 断线以后会遇到一个问题:
我之前已经复制到哪里了?重新连接以后应该从哪里继续?
因此 Redis 给复制历史建立了两个非常关键的标识:
text
replication ID(replid)
replication offset
可以把它们理解成:
text
replid
→ 当前是哪一条复制历史
offset
→ 我在这条复制历史上已经走到了哪里
例如:
text
replid = AAA
offset = 1000
表示:
当前节点处在 AAA 这条复制历史上,并且已经处理到逻辑位置 1000。
如果另一个 Replica 是:
text
replid = AAA
offset = 1200
那么两者属于同一条复制历史,只是后者走得更靠前。
因此:
text
replid + offset
共同描述了某一条复制历史上的一个确定位置。
这里一定要注意:
text
replid 相同
≠ 当前数据一定完全一样
因为 offset 可能不同。
只有:
text
replid 相同
+
offset 相同
才可以认为它们处在同一条复制历史上的同一位置。
4. replication backlog:为什么断线以后可以只补一部分数据
Master 还会在内存中维护一段有限大小的复制历史:
text
replication backlog
可以把它理解成一个有限长度的历史窗口:
text
较旧的数据 较新的数据
↓ ↓
|------------------------------------|
backlog
其中保存最近一段 replication stream。
假设 Replica 原本已经复制到:
text
offset = 10000
然后网络断开了一段时间。
Master 此时已经继续前进到:
text
offset = 11000
Replica 重连以后会携带自己记住的:
text
replid + offset
向 Master 发起 PSYNC。
如果:
text
replid 能对应当前复制历史
+
10001 ~ 11000 这段数据仍然在 backlog 中
那么 Master 没必要重新把整个数据集传一次,只需要把中间缺失的复制流补给 Replica。
这就是:
text
Partial Resynchronization
部分重同步
整个过程可以理解为:
text
Replica:
我属于 AAA 历史
我已经到 10000 了
↓
PSYNC AAA 10000
↓
Master 检查:
AAA 能对应
缺失数据还在 backlog
↓
只发送 10001 ~ 11000
如果 backlog 中已经没有 Replica 所缺失的那一段数据,或者 Replica 携带的复制历史已经无法对应,那么就无法部分同步,只能进入:
text
Full Resynchronization
全量同步
5. 全量同步为什么成本更高
传统的全量同步过程可以粗略理解为:
text
Master 内存数据
↓
fork() 创建后台子进程
↓
生成 RDB 快照
↓
RDB 传给 Replica
↓
Replica 建立完整基准数据集
↓
再继续接收后续复制流
这里 fork() 创建的是:
text
子进程
父子进程通过 Copy-On-Write 的方式共享原有内存页面,Master 父进程继续处理业务,子进程负责生成 RDB。
如果使用传统磁盘式复制,可以将过程继续细化为:
text
Master 内存
↓
RDB
↓
Master 磁盘文件
↓
读取 RDB
↓
网络发送
↓
Replica 接收
↓
建立完整基准状态
Redis 也支持 diskless replication,可以让后台子进程直接通过网络发送 RDB,减少中间磁盘步骤。
无论采用哪一种方式,全量同步的核心特点都没有变化:
它需要重新建立整个数据基准,因此成本远高于只补一小段 backlog 的部分同步。
6. Replica 与上游断开后,并不会自动换一个上游
这里还有一个非常重要的认识。
假设:
text
A Master
↓
B Replica
B 和 A 的复制连接断开。
B 默认的动作不是:
text
"既然 A 连不上,我去其他节点里重新选一个 upstream。"
而是:
text
继续记住 A
↓
反复尝试重连 A
↓
重连成功后 PSYNC
也就是说,Redis 基础复制机制负责的是:
text
"我怎样继续跟随原来的 upstream。"
它本身并没有解决:
text
"原来的 Master 永远回不来了,我应该找谁接班?"
这正是 Sentinel 出现的根本原因。
二、Sentinel 本质上是主从复制之上的"控制层"
1. Sentinel 不是一个新的 KV 数据节点
普通 Redis Server 主要负责:
text
存储业务 KV
处理读写请求
维护主从复制
而 Sentinel 的职责完全不同:
text
监控 Redis 节点
判断 Master 是否故障
协调 Failover
选择新的 Master
重构复制拓扑
向客户端提供当前 Master 地址
因此可以建立这样一个分层模型:
text
Sentinel 控制层
S1 S2 S3
\ | /
\ | /
-----------------
↓
Redis 数据层
Master
/ \
↓ ↓
Replica1 Replica2
普通 Redis 节点属于数据层。
Sentinel 属于高可用控制层。
2. Sentinel 仍然来自 redis-server
Sentinel 并不是另一套完全独立的二进制程序。
Redis 可以通过类似:
bash
redis-server sentinel.conf --sentinel
让同一个 redis-server 以 Sentinel 模式运行。
Sentinel 默认监听:
text
26379
普通 Redis 默认监听:
text
6379
所以 Sentinel 本质上仍然是一个网络服务进程,也可以通过:
bash
redis-cli -p 26379
去查询它的状态。
3. Sentinel 与 Redis 节点之间也是 TCP + RESP
Sentinel 需要不断向 Master / Replica 发送命令获取状态。
所以从网络角色上看:
text
Sentinel
↓ TCP
Redis Server
此时 Sentinel 本身就是 Redis Server 的一个特殊客户端。
它可以发送类似:
text
PING
INFO
ROLE
SENTINEL ...
Redis 的通信仍然基于 RESP 协议。
这里要注意,RESP 虽然很多时候看起来像文本协议,但是其 Bulk String 等结构本身是 binary-safe 的,所以不能简单理解成"Redis 网络协议就是纯文本协议"。
三、Sentinel 是如何知道整个 Redis 拓扑的
1. 配置 Sentinel 时只需要先告诉它 Master
一个 Sentinel 最基本的配置可以是:
conf
sentinel monitor mymaster 172.18.0.10 6379 2
这里包含:
text
mymaster
→ 当前监控的逻辑 Master 名称
172.18.0.10 6379
→ 当前 Master 地址
2
→ quorum
注意:
初始配置中并不要求手工把所有 Replica 都写进去。
Sentinel 会自己从 Master 获取复制拓扑信息。
2. Sentinel 如何发现 Replica
Sentinel 与 Master 建立连接以后,可以周期性获取 Master 的状态信息。
Master 本身知道哪些 Replica 正在复制自己,因此 Sentinel 可以通过 Master 提供的复制信息发现:
text
Master
├── Replica1
├── Replica2
└── Replica3
随后 Sentinel 会直接连接这些 Replica,并分别监控它们的状态。
因此流程可以理解为:
text
Sentinel
↓
先知道 Master
↓
向 Master 查询 replication 信息
↓
发现 Replica 地址
↓
再直接连接各个 Replica
这就是为什么 Sentinel 配置文件最开始只写 Master,也能够最终维护完整的 Redis 主从拓扑。
3. Sentinel 之间如何互相发现
只知道数据节点还不够。
如果有三个 Sentinel:
text
S1
S2
S3
它们还必须知道彼此的存在。
Redis Sentinel 并不要求我们在每个 Sentinel 配置文件中手工写:
text
S1 的地址
S2 的地址
S3 的地址
而是借助 Redis 自己的 Pub/Sub 机制,使用一个特殊频道:
text
__sentinel__:hello
Sentinel 会周期性向被监控的 Master 和 Replica 上的这个频道发布 hello 信息,其中包含自己的:
text
IP
port
runid
当前 Master 配置等信息
其他 Sentinel 同时订阅这个频道,就能够发现新的 Sentinel。
这里一定要避免一个误区:
text
__sentinel__:hello
不是一条 TCP 连接,也不是 Unix 管道。
它是 Redis Pub/Sub 中的一个逻辑频道名称。
整个过程更像:
text
S1 PUBLISH __sentinel__:hello ...
↓
Redis Pub/Sub
↓
S2 / S3 SUBSCRIBE
↓
获得 S1 的信息
当 S2 已经知道 S1 的 IP、端口等信息以后,它们之间还可以建立直接 TCP 通信。
所以 Sentinel 之间不是 RIP 那种"只能把消息一跳一跳向邻居扩散"的路由协议模型,而是:
一旦知道对方地址,就可以直接与对方通信。
四、为什么 Sentinel 不能只部署一个
假设只有一个 Sentinel:
text
S1
↓
Master
如果 S1 自己所在机器故障,那么自动故障转移能力也一起消失。
更麻烦的是,如果 S1 自己发生网络异常:
text
S1 ×── Master
S1 可能认为:
text
"Master 挂了。"
但真实情况可能只是:
text
S1 自己到 Master 的网络出了问题
因此不能让单个观察者的判断直接决定整个 Redis 拓扑。
更合理的做法是使用多个 Sentinel:
text
S1 S2 S3
\ | /
\ | /
Master
它们分别观察 Master,然后交换判断。
实际部署中通常至少使用 3 个 Sentinel,并尽量放在不同机器上。
这里 Sentinel 数量和 Redis 数据节点数量并没有 1:1 的强绑定关系。
例如:
text
3 个 Redis 数据节点
3 个 Sentinel
可以为了部署方便放在三台机器中一一对应,也可以让 Sentinel 部署在其他独立机器节点上。
重点不是"必须一台 Redis 配一个 Sentinel",而是:
Sentinel 本身也应该避免全部依赖同一台机器,否则那台机器一挂,多个 Sentinel 会一起消失。
五、Master 是否故障:先从 SDOWN 到 ODOWN
1. SDOWN:单个 Sentinel 的主观判断
每个 Sentinel 都会独立监控 Master。
例如配置:
conf
sentinel down-after-milliseconds mymaster 5000
意味着如果某个 Sentinel 在足够长的时间内没有从 Master 获得可接受的响应,它会先得到本地判断:
text
SDOWN
Subjectively Down
可以理解为:
"站在我这个 Sentinel 的视角下,我认为 Master 不可用了。"
例如:
text
S1 → Master ×
此时只能说明:
text
S1 认为 Master 有问题
不能说明整个 Sentinel 系统已经确认 Master 故障。
2. 为什么 SDOWN 不能直接 Failover
假设只是:
text
S1 和 Master 之间网络抖动
而:
text
S2 → Master 正常
S3 → Master 正常
如果 S1 一判断 SDOWN 就立即提升 Replica,就可能因为一个局部网络问题制造出两个 Master。
因此 Sentinel 还要继续询问其他 Sentinel:
text
"你们是不是也认为这个 Master 不可达?"
Sentinel 内部会使用类似:
text
SENTINEL is-master-down-by-addr
的机制交换判断。
3. ODOWN:多个 Sentinel 达到 quorum
如果足够多 Sentinel 都认为 Master 不可用,那么 Master 才会进入:
text
ODOWN
Objectively Down
这里关键参数就是:
text
quorum
例如:
conf
sentinel monitor mymaster 172.18.0.10 6379 2
其中最后的:
text
2
就是 quorum。
对于三个 Sentinel:
text
S1
S2
S3
如果至少有两个 Sentinel 对 Master 的故障判断满足条件,就可以把 Master 标记为 ODOWN。
所以可以先建立:
text
SDOWN
→ 一个 Sentinel 自己认为 Master 挂了
ODOWN
→ 足够多 Sentinel 的故障判断达到 quorum
六、quorum 与 majority:Redis Sentinel 中最容易混淆的两个门槛
很多时候会把 quorum 和多数派混成一个东西,但实际上它们解决的是两个不同问题。
1. quorum:先确认"故障是否成立"
quorum 是人为配置的。
例如:
text
Sentinel 数量 = 3
quorum = 2
那么:
text
至少 2 个 Sentinel 认为 Master 不可达
↓
Master 可以进入 ODOWN
所以 quorum 主要控制:
text
故障判断有多敏感
如果 quorum 设置得比较小:
text
更容易达到 ODOWN
如果 quorum 设置得比较大:
text
需要更多 Sentinel 同意
故障判断更加保守
2. majority:决定是否真的允许执行 Failover
Master 被标记为 ODOWN,并不意味着任何 Sentinel 都能直接去改拓扑。
接下来真正执行 Failover 的 Sentinel 还需要得到足够多 Sentinel 的授权。
对于 N 个 Sentinel:
text
majority = floor(N / 2) + 1
例如:
text
3 个 Sentinel → majority = 2
4 个 Sentinel → majority = 3
5 个 Sentinel → majority = 3
这也是为什么实际部署中经常使用奇数个 Sentinel。
例如:
text
3 个 Sentinel
允许失去 1 个仍然拥有多数派
4 个 Sentinel
同样最多只能失去 1 个后仍然拥有 3 票多数派
因此从多数派利用率来看,奇数通常更加划算。
3. Failover 真正需要的授权门槛
可以把真正执行 Failover 所需要的授权数量理解为:
text
max(quorum, majority)
例如:
text
Sentinel = 5
quorum = 2
majority = 3
那么:
text
2 个 Sentinel
→ 可以触发 ODOWN
但真正执行 Failover
→ 仍然至少需要 3 个 Sentinel 授权
反过来:
text
Sentinel = 5
quorum = 5
majority = 3
那么 Failover 实际上需要:
text
5
因此可以把两个参数这样记:
text
quorum
→ 故障确认门槛
majority
→ 系统为了避免少数派独自修改拓扑而提供的安全底线
七、为什么必须选一个 Sentinel Leader
达到 ODOWN 以后,还有一个问题:
text
S1、S2、S3 都知道 Master 挂了
那到底谁来执行:
text
选 Replica
提升 Master
修改其他 Replica
传播新配置
如果三个 Sentinel 同时执行,很容易出现不同 Sentinel 同时修改拓扑的问题。
因此 Redis Sentinel 在每轮 Failover 中,会先选出一个 Sentinel Leader。
这里一定要把两个"选举"区分开:
text
Sentinel Leader Election
→ 选谁来执行 Failover
Replica Selection
→ 选哪个 Replica 成为新的 Master
它们不是一回事。
八、epoch:给每一轮故障转移建立"轮次编号"
Sentinel Leader Election 不能只靠一句:
text
"我投给 S1。"
因为网络中可能存在延迟消息。
假设上一轮投票是:
text
epoch = 10
某个旧投票由于网络延迟,一直到下一轮:
text
epoch = 11
才送到。
如果没有轮次编号,就可能把旧投票误当成当前轮次的有效投票。
因此 Sentinel 使用:
text
epoch
标识当前 Failover / Leader Election 所属的逻辑轮次。
可以这样区分几个 ID:
text
runid
→ 当前是哪一个 Redis / Sentinel 进程实例
replid
→ 当前是哪一条 Redis 复制历史
epoch
→ 当前是哪一轮 Sentinel 故障转移 / 选举
九、Sentinel Leader Election 到底是怎么投票的
假设三个 Sentinel:
text
S1
S2
S3
都进入同一轮故障转移。
一个准备竞选 Leader 的 Sentinel 会向其他 Sentinel 发送内部请求。
可以将请求形式理解为:
text
SENTINEL is-master-down-by-addr
<master-ip>
<master-port>
<epoch>
<candidate-runid>
这里一个容易写错的地方是:
请求中的 IP 和端口描述的是正在判断的 Master,不是候选 Sentinel 自己的 IP 和端口。
候选 Sentinel 的身份主要通过:
text
candidate-runid
表达。
每一个 Sentinel 在同一个 epoch 中只能对同一个 Master 的 Failover 授权给一个候选者。
可以先用一个直观模型理解:
text
S1 和 S2 几乎同时竞选
S1 → 自己投 S1
S2 → 自己投 S2
S3 先处理到 S1 的有效请求
→ S3 投 S1
最终:
text
S1 = 2 票
S2 = 1 票
对于三个 Sentinel:
text
majority = 2
所以 S1 获得 Leader 执行权。
这里的安全性不依赖:
text
"大家运气好,刚好没有打平。"
而是来自:
text
一个 Sentinel 每个 epoch 只能授权一个候选者
+
Leader 必须获得严格多数派
因此在同一个 epoch 中,不可能同时存在两个都获得严格多数派授权的合法 Leader。
例如四个 Sentinel 出现:
text
S1 = 2 票
S2 = 2 票
当然可以发生。
但:
text
majority = 3
于是两边都不能成为 Leader。
也就是说:
Sentinel 并不要求"投票数量永远不会打平",而是要求"没有达到合法门槛的人不能执行 Failover"。
1. Sentinel 不需要维护一个全局实时票数表
Leader Election 也不是:
text
所有 Sentinel
↓
先把所有投票同步到一个中央节点
↓
计算全局排行榜
候选 Sentinel 只需要收集自己收到的授权回复,然后判断:
text
我的有效授权数量
是否已经达到需要的门槛
所以一个候选者不需要实时知道:
text
"另外一个候选者现在究竟有几票。"
它真正关心的是:
text
"我自己的票够不够。"
十、Leader 选出来以后,如何选择新的 Master
Leader 获得 Failover 执行权以后,才进入另一个问题:
text
哪个 Replica 最适合接班?
这个过程和 Sentinel Leader Election 完全不同。
Leader Election 解决的是:
text
谁来执行
Replica Selection 解决的是:
text
谁最适合成为新的 Master
Replica 的选择是真正会认真比较节点状态的。
整体可以先记成四步:
text
Replica 候选集合
↓
① 失联时间资格过滤
↓
② replica-priority
↓
③ replication offset
↓
④ runid
↓
选出新的 Master
1. 第一关:根据与旧 Master 的失联时间淘汰不可靠 Replica
所谓失联时间,本质上就是:
text
Replica 与旧 Master 的复制连接
已经断开了多久
如果一个 Replica 很早就已经和旧 Master 断开:
text
Master offset = 100000
Replica-A offset ≈ 99900
Replica-B offset = 60000
那么 B 的数据显然可能已经落后很多。
Sentinel 不会简单地把所有 Replica 按:
text
断联 1 秒
断联 2 秒
断联 3 秒
排序后直接选最短的。
失联时间首先是一个:
text
资格过滤条件
官方规则可以近似表示为:
text
Replica 失联时间
>
(down-after-milliseconds * 10)
+
当前 Sentinel 认为 Master 处于 SDOWN 的持续时间
那么这个 Replica 会被直接认为不适合 Failover。
所以第一关应该理解成:
text
所有 Replica
↓
淘汰失联过久的
↓
留下仍然足够可靠的候选者
2. 第二关:比较 replica-priority
对于通过第一关的 Replica,接下来比较:
text
replica-priority
这是一个人为配置项。
例如:
text
Replica-A priority = 10
Replica-B priority = 100
数字越小:
text
优先级越高
因此 A 更优先。
可以把它理解为管理员提前告诉 Sentinel:
如果以后需要 Failover,我更希望哪些 Replica 优先成为 Master。
还有一个特殊值:
text
replica-priority = 0
表示:
这个 Replica 永远不要被 Sentinel 提升为 Master。
但是它仍然可以在 Failover 以后被重新配置为新 Master 的 Replica。
3. 第三关:比较 replication offset
如果:
text
priority 相同
那么 Sentinel 会继续比较:
text
replication offset
例如:
text
Replica-A offset = 100000
Replica-B offset = 105000
那么优先选择 B。
原因非常直接:
text
offset 越大
↓
说明从旧 Master 获取到的复制流越多
↓
数据通常更接近旧 Master 故障前的最新状态
↓
潜在数据损失更少
因此这一关才是真正意义上的:
text
谁的数据更新
4. 第四关:offset 也相同,就比较 runid
如果多个 Replica:
text
失联时间资格都通过
priority 一样
offset 也一样
那么已经没有明显业务意义上的优劣差别了。
Redis 最后会比较:
text
runid
runid 是 Redis 进程实例启动时生成的随机标识,通常表现为一串 40 位十六进制字符串。
它表示的是:
text
"当前这个 Redis / Sentinel 进程实例是谁"
并不代表节点性能。
最后选择:
text
字典序更小的 runid
作为确定性的兜底规则。
所以:
text
runid 更小
≠ 节点更优秀
这里只是为了:
text
避免前面完全打平以后随机选择
让最终结果更加稳定、确定。
十一、Failover 真正执行时发生了什么
假设 Leader 最终选中了:
text
Replica-B
那么整体过程可以理解为:
text
Leader Sentinel
↓
选择 Replica-B
↓
发送 REPLICAOF NO ONE
↓
Replica-B 解除原 upstream
↓
Replica-B → Master
↓
等待 INFO 中确认角色真的已经变成 Master
↓
修改其他 Replica 的 upstream
↓
让它们全部复制 Replica-B
↓
发布新的 Master 配置
这里不是 Leader 一发命令就立刻认为成功。
Sentinel 会等待真正观察到 Replica 已经变成 Master,然后才继续推进 Failover 状态机。
十二、新 Master 为什么必须生成新的 replid
这里是 Sentinel Failover 和 Redis 基础复制真正连接起来的地方。
假设原来:
text
A Master
replid = AAA
/ \
↓ ↓
B Replica C Replica
A 发生网络分区以后,并不一定真的死了。
可能出现:
text
A 仍然活着
但是 B、C、足够多 Sentinel 已经无法访问 A
Sentinel 最终将 B 提升为新的 Master。
如果 B 还继续沿用:
text
AAA
就可能出现:
text
A:仍然认为自己是 Master
AAA + offset 10001
写入 SET x 1
B:已经被提升成新 Master
AAA + offset 10001
写入 SET x 2
此时:
text
replid 相同
offset 相同
但数据却不同。
这样就破坏了:
text
replid + offset
唯一确定复制历史位置
这一基本规则。
因此 Replica 一旦被提升为新的 Master,就必须产生新的 replication ID:
text
AAA
↓ Failover
BBB
表示:
从这一刻开始,产生了一条新的复制历史。
十三、为什么新 Master 还要保留旧 replid
如果 B 晋升以后只保留:
text
master_replid = BBB
把原来的:
text
AAA
完全忘掉,会发生什么?
C 原本属于旧 Master A 的复制历史:
text
C:
replid = AAA
offset = 9800
Failover 后 Sentinel 告诉 C:
text
你的新 upstream 是 B
于是 C 去连接 B,并发起:
text
PSYNC AAA 9800
如果 B 只认识:
text
BBB
那么 B 会认为:
text
你的复制历史和我完全不一样
于是只能:
text
FULLRESYNC
但实际上 B 和 C 在故障发生前,本来都是 A 的 Replica,它们的数据通常非常接近。
例如:
text
B = AAA + 10000
C = AAA + 9800
差距可能只有 200 个 offset 对应的数据。
因为角色切换就让 C 重新接收整个数据集,明显没有必要。
因此 B 晋升时会做一件非常关键的事情:
text
master_replid = BBB
master_replid2 = AAA
同时保存旧历史的有效边界:
text
second_repl_offset
可以理解成:
text
当前历史:BBB
上一条历史:AAA
AAA 在我这里有效到某个历史切换位置
于是 C 来:
text
PSYNC AAA 9800
B 可以判断:
text
AAA == master_replid2
↓
这条旧历史我认识
9800 仍然位于旧历史可衔接范围内
↓
并且需要的数据还在 backlog
↓
可以 Partial Resync
所以:
新 replid 用来明确"复制历史已经分叉";旧 replid2 则给故障转移前的 Replica 留了一座连接旧历史和新历史的桥。
它不是"假装新旧历史还是同一条",而是:
text
明确承认已经进入新历史
+
仍然记住上一段历史
这样其他 Replica 才有机会避免昂贵的全量同步。
1. 用 Git 分支理解 replid2
可以把共同历史理解成:
text
A ─ B ─ C
\
D ─ E
当前新 Master 已经走到了:
text
D → E
但是它没有忘记:
text
A → B → C
这一段祖先历史。
所以一个旧 Replica 说:
text
"我走到 C 这里了。"
新 Master 仍然知道:
text
"C 是我过去历史上的位置,你从这里继续往后追即可。"
而不是直接回答:
text
"C 是什么?我完全不认识,重新传整个数据集。"
十四、旧 Master 恢复以后会发生什么
假设原来:
text
A Master
├── B Replica
└── C Replica
A 挂掉或者被网络隔离以后,B 被提升:
text
B New Master
└── C Replica
那么 A 在新拓扑中的身份已经不再是 Master。
即使 A 之后重新启动,它本地仍然可能暂时认为:
text
role:master
因为它自己的启动配置并不知道这段时间 Sentinel 已经完成了一轮 Failover。
但是 Sentinel 保存的最新逻辑拓扑已经变成:
text
B = Master
A = 应该成为 B 的 Replica
C = B 的 Replica
这里连接方向一定要搞清楚。
不是:
text
A 重启
↓
A 主动寻找 Sentinel
↓
向 Sentinel 报到
而是:
text
Sentinel 一直知道 A 的地址
↓
不断尝试监控 A
↓
发现 A 又可达
↓
Sentinel 主动连接 A
随后 Sentinel 会把当前正确拓扑重新施加到 A 身上,例如让 A:
text
REPLICAOF B
于是:
text
A:Master
↓
A:Replica of B
然后才轮到 A 作为 Replica:
text
A
↓ 主动建立复制连接
B New Master
↓
PSYNC replid offset
最终 A 重新加入当前复制拓扑。
所以 Sentinel 的一个重要思想就是:
它会不断尝试把自己所知道的最新逻辑配置重新施加到实际 Redis 节点上。
旧 Master Failover 后重新回来,也会被重新配置成新 Master 的 Replica。
十五、旧 Master 恢复后一定全量同步吗
不一定。
这里还要分场景。
1. 旧 Master 真正宕机,没有继续产生自己的写入
假设 A 最后停在:
text
AAA + offset 9800
B 晋升以后保留:
text
master_replid = BBB
master_replid2 = AAA
并且 backlog 仍然能够覆盖 A 所需要的数据。
那么 A 被降级为 Replica 后,有机会:
text
PSYNC AAA 9800
↓
Partial Resync
所以:
text
旧 Master 恢复
≠ 必然 Full Resync
2. 旧 Master 在网络隔离期间继续接受写请求
这个情况就更危险。
假设在分叉点:
text
AAA + offset 10000
之后形成两条历史:
text
共同历史 AAA
↓
offset 10000
/ \
/ \
↓ ↓
旧 Master A 新 Master B
继续接受写 replid = BBB
AAA 自己向前 新历史继续向前
A 隔离期间可能走到了:
text
AAA + offset 12000
但 B 只知道旧 AAA 历史在 Failover 切换点附近有效。
所以:
text
A 的 offset 更大
并不代表:
text
A 的数据更正确
因为 A 后面的数据属于另一条已经分叉的历史。
这时候就可能无法进行 Partial Resync,而需要重新以新 Master B 的数据为准建立基准。
A 隔离期间自己接受的那些写入不会自动和 B 做 merge,而可能在重新同步时被丢弃。
十六、网络隔离时 Sentinel 到底会不会 Failover
这里要分两种完全不同的网络问题。
1. 只有 Master ↔ Replica 复制链路断开
例如:
text
Sentinel
│
│ 正常
↓
Master
× ×
/ \
Replica1 Replica2
也就是:
text
Sentinel → Master:正常
Replica → Master:失败
站在 Replica 的视角:
text
我的 upstream 不可达
它们会不断重连 Master。
但是站在 Sentinel 的视角:
text
Master 仍然正常响应 PING / INFO
那么 Sentinel 不会形成 Master 的 SDOWN / ODOWN,也就不会触发 Failover。
这并不是系统"卡死了只能人工处理",而是一个合理选择。
因为真正故障的是:
text
复制链路
而不是 Master 本身。
如果网络稍后恢复:
text
Replica
↓
重新连接 Master
↓
PSYNC
↓
恢复复制
通常不需要人工换 Master。
2. 足够多 Sentinel 也无法访问 Master
另一种情况:
text
网络分区 A:
旧 Master + 少数 Sentinel
网络分区 B:
Replica + 多数 Sentinel
如果多数 Sentinel 无法访问旧 Master,达到:
text
quorum
+
majority
那么它们可能在分区 B 中完成 Failover,把一个 Replica 提升为新的 Master。
但旧 Master 本身可能仍然活着,并继续服务自己这一侧的客户端。
于是短时间内会出现:
text
旧 Master A
继续接受写
新 Master B
也开始接受写
这就是网络分区中最危险的历史分叉场景之一。
分区恢复后,Sentinel 会把旧 Master A 降级为 B 的 Replica。
A 在隔离期间自己接受的写入并不会自动 merge 到 B,而可能被丢弃。
所以 Redis Sentinel 虽然提供高可用,但 Redis 基础复制本身仍然是异步复制,Failover 场景下不能简单理解成"绝对零数据丢失"。
十七、使用 Docker 搭建 3 Redis + 3 Sentinel 实验环境
前面的机制全部理解以后,下面通过一次真实实验把整个流程串起来。
需要先说明:
这里 6 个容器全部运行在同一台云服务器上,只适合验证机制,不是真正意义上的高可用部署。
因为物理宿主机一旦挂掉,6 个容器会一起消失。
1. 创建 Docker 网络
为了让多个容器处在同一个虚拟局域网中,首先创建:
bash
docker network create redis-sentinel-net
这里可以把 Docker bridge network 理解成一个虚拟交换网络:
text
redis-master 172.18.0.10
redis-replica1 172.18.0.11
redis-replica2 172.18.0.12
redis-sentinel1 172.18.0.21
redis-sentinel2 172.18.0.22
redis-sentinel3 172.18.0.23
每个容器都有自己的 Network Namespace,因此虽然多个容器内部都监听:
text
6379
也不会冲突。
2. 创建 Master
实验中先启动:
bash
docker run -d \
--name redis-master \
--network redis-sentinel-net \
--ip 172.18.0.10 \
redis:7-alpine \
redis-server \
--bind 0.0.0.0 \
--port 6379 \
--protected-mode no
随后查看:
bash
docker exec -it redis-master redis-cli INFO replication
刚启动时 Master 没有任何 Replica,因此:
text
role:master
connected_slaves:0
repl_backlog_active:0
实验截图如下:

这里还能看到当前 Master 已经拥有自己的:
text
master_replid
但由于还没有 Replica:
text
repl_backlog_active:0
3. 创建 Replica1
接下来创建第一个 Replica:
bash
docker run -d \
--name redis-replica1 \
--network redis-sentinel-net \
--ip 172.18.0.11 \
redis:7-alpine \
redis-server \
--bind 0.0.0.0 \
--port 6379 \
--protected-mode no \
--replicaof 172.18.0.10 6379
查看 Replica1:
bash
docker exec -it redis-replica1 redis-cli INFO replication
可以看到:
text
role:slave
master_host:172.18.0.10
master_port:6379
master_link_status:up
说明 Replica1 已经成功连接到 Master。

再回头查看 Master:
text
connected_slaves:1
slave0:ip=172.18.0.11,port=6379,state=online
同时:
text
repl_backlog_active:1
说明建立复制关系以后,Master 的 replication backlog 已经开始工作。

4. 创建 Replica2
继续创建第二个 Replica:
bash
docker run -d \
--name redis-replica2 \
--network redis-sentinel-net \
--ip 172.18.0.12 \
redis:7-alpine \
redis-server \
--bind 0.0.0.0 \
--port 6379 \
--protected-mode no \
--replicaof 172.18.0.10 6379
检查:
bash
docker exec -it redis-replica2 redis-cli INFO replication
可以看到 Replica2 同样已经:
text
role:slave
master_host:172.18.0.10
master_link_status:up
并且其 master_replid 与整个复制组当前使用的复制历史一致。

至此数据层完成:
text
172.18.0.10
Master
/ \
↓ ↓
172.18.0.11 172.18.0.12
Replica1 Replica2
十八、创建三个 Sentinel
1. Sentinel1 配置
创建 Sentinel1 的配置:
conf
port 26379
bind 0.0.0.0
protected-mode no
sentinel monitor mymaster 172.18.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
这里:
text
monitor ... 2
→ quorum = 2
down-after-milliseconds = 5000
→ 为了实验快速观察 SDOWN,将检测时间设置为 5 秒
failover-timeout = 60000
→ Failover 相关超时窗口
parallel-syncs = 1
→ Failover 后一次允许一个 Replica 与新 Master 并行重同步

然后启动:
bash
docker run -d \
--name redis-sentinel1 \
--network redis-sentinel-net \
--ip 172.18.0.21 \
-v ~/redis-sentinel-lab/sentinel1:/etc/redis \
redis:7-alpine \
redis-server /etc/redis/sentinel.conf --sentinel
日志中可以观察到:
text
+monitor master mymaster 172.18.0.10 6379 quorum 2
+slave ... 172.18.0.11:6379
+slave ... 172.18.0.12:6379
这直接验证了:
text
我们只配置了 Master
↓
Sentinel 自动发现两个 Replica

2. Sentinel2:验证 Sentinel 之间的自动发现
Sentinel2 使用相同监控配置,地址为:
text
172.18.0.22:26379
启动以后,它除了发现两个 Replica,还会出现类似:
text
+sentinel sentinel <S1-runid> 172.18.0.21 26379 @ mymaster ...
这说明 S2 自动发现了 S1。

查看 Sentinel2 日志,可以观察到:
text
+slave ... 172.18.0.11
+slave ... 172.18.0.12
+sentinel sentinel ... 172.18.0.21 26379
这正是:
text
Replica 自动发现
+
__sentinel__:hello Sentinel 自动发现
两套机制同时工作。

3. 创建 Sentinel3
第三个 Sentinel 地址设置为:
text
172.18.0.23:26379
启动以后最终得到:
text
S1 = 172.18.0.21
S2 = 172.18.0.22
S3 = 172.18.0.23

此时整个实验环境为:
text
Redis 数据层
172.18.0.10
Master
/ \
↓ ↓
172.18.0.11 172.18.0.12
Replica1 Replica2
Sentinel 控制层
172.18.0.21 172.18.0.22 172.18.0.23
S1 S2 S3
quorum = 2
majority = 2
十九、先验证 Sentinel 集群具备 Failover 条件
通过:
bash
docker exec -it redis-sentinel1 \
redis-cli -p 26379 SENTINEL CKQUORUM mymaster
得到:
text
OK 3 usable Sentinels. Quorum and failover authorization can be reached
这句话其实正好对应前面的两层机制:
text
3 个 Sentinel 当前都可用
↓
quorum = 2 可以达到
↓
majority = 2 也可以达到
↓
当前具备自动 Failover 条件
随后在 Master 写入测试数据:
bash
docker exec -it redis-master \
redis-cli SET sentinel:test before-failover
两个 Replica 都能读取:
text
"before-failover"
说明 Failover 前三个数据节点处于一致的基准状态。

二十、真正制造 Master 故障
直接杀掉 Master:
bash
docker kill redis-master
等待 Sentinel 完成判断与切换以后,通过:
bash
docker exec -it redis-sentinel1 \
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
得到:
text
172.18.0.12
6379
这说明:
text
旧 Master:172.18.0.10
↓
新 Master:172.18.0.12
随后查看两个 Redis:
text
172.18.0.12:
role:master
connected_slaves:1
slave0:ip=172.18.0.11
172.18.0.11:
role:slave
master_host:172.18.0.12
master_link_status:up
所以最终拓扑真正变成:
text
172.18.0.10
×
172.18.0.12
Master
│
↓
172.18.0.11
Replica
这说明 Sentinel 并不只是修改了自己内部"Master 地址"的记录,而是真的完成了:
text
Replica2 → Master
Replica1 → 改为复制 Replica2
二十一、通过真实日志完整还原一次 Failover
这次实验最有价值的部分就是 Sentinel1 日志。
核心日志如下:
text
+sdown master mymaster 172.18.0.10 6379
+odown master mymaster 172.18.0.10 6379 #quorum 3/2
+new-epoch 1
+try-failover master mymaster 172.18.0.10 6379
+vote-for-leader aabb... 1
...
+elected-leader master mymaster 172.18.0.10 6379
+failover-state-select-slave master mymaster ...
+selected-slave slave 172.18.0.12:6379 ...
+failover-state-send-slaveof-noone slave 172.18.0.12:6379 ...
+failover-state-wait-promotion slave 172.18.0.12:6379 ...
+promoted-slave slave 172.18.0.12:6379 ...
+failover-state-reconf-slaves master mymaster ...
+slave-reconf-sent slave 172.18.0.11:6379 ...
+slave-reconf-inprog slave 172.18.0.11:6379 ...
+slave-reconf-done slave 172.18.0.11:6379 ...
+failover-end master mymaster ...
+switch-master mymaster 172.18.0.10 6379 172.18.0.12 6379
+slave slave 172.18.0.11:6379 ... @ mymaster 172.18.0.12 6379
+slave slave 172.18.0.10:6379 ... @ mymaster 172.18.0.12 6379
现在可以把它和理论逐行对应。
1. +sdown:单个 Sentinel 首先认为旧 Master 不可达
text
+sdown master mymaster 172.18.0.10 6379
表示:
text
当前 Sentinel
↓
超过 down-after-milliseconds
没有获得正常响应
↓
SDOWN
三个 Sentinel 的 SDOWN 时间非常接近,但不是完全一致。
这很正常,因为它们是三个独立进程,各自维护自己的定时器和网络状态。
2. +odown ... #quorum 3/2
text
+odown ... #quorum 3/2
可以理解成:
text
当前获得 3 个 Sentinel 的故障判断
配置要求 quorum = 2
↓
3 >= 2
↓
ODOWN
另一个 Sentinel 日志中也可能出现:
text
#quorum 2/2
因为各 Sentinel 接收到其他节点回复的具体时刻并不完全相同。
它们并不需要在同一时刻拥有完全同步的局部状态。
3. +new-epoch 1
text
+new-epoch 1
表示开始新的故障转移逻辑轮次。
由于这是实验环境中的第一次 Failover,因此:
text
epoch = 1
4. +try-failover 与真实 Leader 投票
这次日志非常有意思,因为 S1 和 S2 几乎同时尝试参与 Failover。
S1:
text
+vote-for-leader aabb... 1
表示 S1 在 epoch=1 给自己授权。
S2:
text
+vote-for-leader 2cfd... 1
表示 S2 也给自己授权。
然后 S3:
text
c8df... voted for aabb... 1
于是形成:
text
S1:S1 + S3 = 2
S2:S2 = 1
对于三个 Sentinel:
text
majority = 2
所以 S1 最终出现:
text
+elected-leader
这次真实实验正好证明了:
text
多个 Sentinel 可以同时尝试竞选
↓
但是只有获得合法授权门槛的 Sentinel
↓
才能真正执行 Failover
5. +selected-slave 172.18.0.12
S1 成为 Leader 后进入:
text
+failover-state-select-slave
随后:
text
+selected-slave slave 172.18.0.12:6379
说明最终选择 Replica2 作为新 Master。
在这次实验里两个 Replica:
text
replica-priority = 100
都相同。
它们故障前的 replication offset 也非常接近。
如果在 Leader 做最终比较时前面的条件都一致,就会继续使用 runid 的字典序规则做最终确定性选择。
6. send-slaveof-noone:真正提升 Replica
日志:
text
+failover-state-send-slaveof-noone
含义非常直观。
Leader 要让:
text
172.18.0.12
不再复制任何上游,也就是执行与:
text
REPLICAOF NO ONE
等价的角色切换。
随后进入:
text
+failover-state-wait-promotion
表示 Sentinel 正在等待 .12 真正完成角色变化。
直到看到:
text
+promoted-slave
才说明 .12 已经真正成为 Master。
7. slave-reconf-*:让其他 Replica 改上游
新 Master 已经产生以后:
text
172.18.0.12 = Master
剩下的 .11 还在复制旧 .10。
所以 Leader 开始:
text
+failover-state-reconf-slaves
随后:
text
+slave-reconf-sent
+slave-reconf-inprog
+slave-reconf-done
最终 .11 的配置变成:
text
master_host:172.18.0.12
于是:
text
原来:
.11 → .10
现在:
.11 → .12
8. +switch-master:新的拓扑正式成立
最后出现:
text
+switch-master mymaster
172.18.0.10 6379
172.18.0.12 6379
意味着 mymaster 的逻辑地址正式从:
text
172.18.0.10:6379
切换为:
text
172.18.0.12:6379
因此客户端以后通过 Sentinel 查询:
text
SENTINEL get-master-addr-by-name mymaster
就会得到新的 Master 地址。
9. 为什么旧 .10 已经被记成 Replica
Failover 日志后面还会看到:
text
+slave slave 172.18.0.10:6379 ... @ mymaster 172.18.0.12 6379
这非常关键。
虽然 .10 此时仍然被 docker kill,根本没有恢复,但 Sentinel 在新的逻辑拓扑中已经认定:
text
172.18.0.10
以后恢复
↓
应该成为 172.18.0.12 的 Replica
所以后续才会出现:
text
+sdown slave 172.18.0.10:6379
注意此时它已经不是:
text
"不可达的 Master"
而是:
text
"当前新拓扑中暂时不可达的 Replica"
这也为后面旧 Master 恢复后被自动降级重新接入新拓扑做了铺垫。
二十二、Failover 后的 replid 实验结果
新 Master .12 的 INFO replication 出现:
text
master_replid:f835620d...
master_replid2:136e432c...
second_repl_offset:126533
其中旧 Master .10 原来的 replication ID 正是:
text
136e432c...
也就是说实验真实验证了:
text
旧 Master replid
↓
Replica2 晋升
↓
新 master_replid = 新 ID
master_replid2 = 旧 ID
这正是前面所讨论的:
text
新的 replid
→ 表示已经进入新的复制历史
replid2
→ 保留故障转移前的旧复制历史身份
second_repl_offset
→ 记录旧历史可以安全匹配的边界
从而让其他旧 Replica 在重新连接新 Master 时,有机会继续使用旧 replid + offset 进行部分同步,而不是因为 Master 身份发生切换就全部 Full Resync。
二十三、把整个 Sentinel 心智模型最终串起来
到这里,可以把 Redis 主从复制与 Sentinel 的关系完整地整理成下面这条逻辑链。
text
Redis Master
负责接收写请求
↓
产生 replication stream
↓
Replica 跟随 Master
↓
replid 标识复制历史
offset 标识历史位置
backlog 保存最近一段历史
↓
普通复制链路断开
↓
Replica 自动重连原 upstream
↓
PSYNC replid offset
↓
能接 backlog
→ Partial Resync
不能接
→ Full Resync
但如果:
text
Master 真正不可用
基础复制机制不会自己重新选 Master,因此需要 Sentinel:
text
Sentinel 持续监控 Master
↓
单个 Sentinel 判断 Master 不可达
↓
SDOWN
↓
向其他 Sentinel 询问
↓
达到 quorum
↓
ODOWN
↓
进入新的 epoch
↓
多个 Sentinel 可能同时尝试竞选
↓
一个 Sentinel 每个 epoch 只能授权一个候选者
↓
候选者达到多数派授权门槛
↓
成为 Leader
Leader 获得执行权以后:
text
先检查 Replica 与旧 Master 的失联时间
↓
失联过久
→ 淘汰
↓
比较 replica-priority
↓
优先级一样
→ 比 replication offset
↓
offset 一样
→ 比 runid
↓
选出一个 Replica
然后:
text
REPLICAOF NO ONE
↓
Replica → New Master
↓
产生新的 replid
↓
旧 replid 保存到 replid2
↓
修改其他 Replica 的 upstream
↓
向其他 Sentinel 传播新配置
↓
客户端通过 Sentinel 获得新 Master
旧 Master 后续恢复:
text
Sentinel 再次发现旧 Master 可达
↓
发现它与当前逻辑拓扑不一致
↓
将旧 Master 配置成新 Master 的 Replica
↓
旧 Master 主动连接新 Master
↓
PSYNC
↓
能够利用旧历史 + backlog
→ Partial Resync
无法继续旧历史
→ Full Resync
二十四、几个最容易混淆的概念
最后再把整个学习过程中最容易混淆的几个概念集中区分一下。
1. runid / replid / epoch
text
runid
→ 一个 Redis / Sentinel 进程实例是谁
replid
→ 当前属于哪一条复制历史
epoch
→ 当前是哪一轮 Sentinel Failover / Leader Election
2. quorum / majority
text
quorum
→ 人工配置
→ Master 从 SDOWN 推进到 ODOWN 所需要的故障判断数量
majority
→ floor(N/2)+1
→ Failover Leader 至少需要的多数派安全门槛
真正执行 Failover 的授权数量可以理解为:
text
max(quorum, majority)
3. Sentinel Leader Election / Replica Selection
text
Sentinel Leader Election
→ 决定"谁来执行 Failover"
→ 核心是 epoch + 投票授权 + 多数派
Replica Selection
→ 决定"哪个 Replica 接班"
→ 核心是节点状态排序
Replica Selection 的顺序:
text
失联时间资格过滤
↓
replica-priority
↓
replication offset
↓
runid
4. Sentinel 与 Redis 节点的连接 / Redis 主从复制连接
这是两套不同用途的 TCP 连接。
控制连接:
text
Sentinel
↓
Redis 数据节点
用于:
text
监控
PING / INFO
拓扑修改
复制连接:
text
Replica
↓
Master / upstream
用于:
text
PSYNC
复制流传输
所以旧 Master 恢复以后不是主动寻找 Sentinel,而是 Sentinel 重新发现它,再将它配置成 Replica。随后旧 Master 才以 Replica 身份主动连接新的 upstream。
5. __sentinel__:hello 不是"管道"
它是:
text
Redis Pub/Sub Channel
也就是一个逻辑消息主题。
Sentinel 使用它:
text
发布自己的 IP / port / runid / 配置信息
↓
其他 Sentinel 订阅
↓
完成自动发现和配置传播
总结
Redis 主从复制首先解决的是:
text
如何让多个 Redis 节点拥有相近的数据副本
它通过:
text
replid
+
offset
+
replication backlog
+
PSYNC
使 Replica 在复制连接中断后能够尽量进行部分重同步,而不是每次都重新传整个数据集。
但是主从复制本身不会在 Master 永久不可用时自动重构拓扑。
因此 Sentinel 在主从复制之上增加了一层控制逻辑:
text
监控
↓
故障判断
↓
ODOWN
↓
Leader Election
↓
Replica Selection
↓
Failover
↓
配置传播
↓
旧节点重新加入
真正理解 Sentinel 的关键,不是分别记住这些名词,而是理解它们为什么必须依次出现。
单个 Sentinel 的判断可能出错,所以需要:
text
SDOWN → quorum → ODOWN
多个 Sentinel 不能同时改拓扑,所以需要:
text
epoch + majority + Leader Election
新的 Master 不能随便选,所以需要:
text
失联时间
→ priority
→ offset
→ runid
Replica 被提升以后复制历史可能和旧 Master 分叉,所以必须:
text
生成新的 replid
但是其他 Replica 本来和新 Master 的数据非常接近,又没有必要全部重新做全量同步,所以还需要:
text
master_replid2
+
second_repl_offset
+
backlog
最终,Sentinel 真正解决的是:
在普通 Redis 主从复制已经能够"复制数据"的基础上,再增加一套分布式控制机制,让系统能够自动判断 Master 故障、唯一确定 Failover 执行者、挑选新的 Master,并把整个复制拓扑重新收敛到一个新的稳定状态。
