Redis主从模式虽然支持数据备份与读写分离,但存在三大核心缺陷:1. 故障切换依赖人工(主节点宕机需手动提升从节点);2. 监控能力缺失(无法自动检测节点异常);3. 脑裂风险(网络分区可能导致双主数据冲突)。这些缺陷使得系统可用性难以保障,尤其在分布式场景下隐患显著。
为此引入Redis哨兵模式:通过独立部署的哨兵集群(至少3节点)实现:
- 实时监控:多哨兵协同检测节点健康状态
- 自动故障转移:主节点宕机时,基于Raft算法选举新主并同步拓扑
- 防脑裂机制:客观下线判断(需多数哨兵达成共识)避免误判
哨兵模式将故障恢复时间从人工介入的分钟级压缩至秒级,构建了完整的高可用体系。
三大定时监控任务
信息同步任务(10秒/次)
作用:通过INFO
命令获取Redis集群的实时拓扑结构,包括主从节点的角色变化、新增从节点发现等。该任务是哨兵感知集群动态的基础。
技术实现:
- 每个哨兵节点每10秒向主节点发送
INFO
命令,主节点返回包含从节点列表的响应 - 根据响应更新本地拓扑信息,并与从节点建立连接
- 发现新从节点时,自动将其纳入监控范围
状态广播任务(2秒/次)
作用:通过Redis的发布订阅机制,实现哨兵节点间的状态同步与协作,具体包括:
- 交换节点状态判断:广播当前哨兵对主节点是否下线的判断结果
- 发现新哨兵节点:通过解析
__sentinel__:hello
频道的消息,动态加入新哨兵到集群 - 触发Leader选举:当检测到主节点异常时,通过消息触发Raft选举流程
技术实现:
- 哨兵节点既是发布者(发送自身状态),也是订阅者(接收其他哨兵状态)
- 使用Redis的
PUBLISH
/SUBSCRIBE
机制,避免直接节点间通信的复杂性
心跳检测任务(1秒/次)
作用:通过PING
命令实现节点存活检测,触发主观下线(SDOWN)判断流程。
技术实现:
- 每1秒向所有主/从节点及哨兵节点发送
PING
命令 - 若节点在
down-after-milliseconds
(默认30秒)内未响应,标记为主观下线 - 当主节点被标记为主观下线时,启动客观下线(ODOWN) 投票
下线流程
Redis Sentinel的下线流程分为主观下线和客观下线两个核心阶段,最终触发故障转移。
主观下线(SDOWN)
主观下线:Subjectively Down,简称SDOWN。
触发条件:单个Sentinel节点通过心跳检测(PING
命令)发现主节点或从节点无响应,且超时时间超过配置的down-after-milliseconds
阈值(默认30秒)。
技术细节:
- Sentinel每秒向所有主、从节点发送
PING
命令,若节点在超时时间内未返回有效响应(如PONG
),则标记为主观下线。 - 有效响应包括
PONG
、LOADING
、MASTERDOWN
;无效响应或无响应则触发判断。
特点:
- 仅代表单个Sentinel的局部判断,可能存在误判(如网络抖动)。
- 从节点被标记为主观下线后不会触发后续流程,仅主节点需进入客观下线阶段。
客观下线(ODOWN)
客观下线:Objectively Down,简称ODOWN。
触发条件:当足够数量的Sentinel节点(由quorum
参数决定)均认为主节点主观下线时,主节点被标记为客观下线,成为故障转移的触发条件。
技术细节:
- 协商机制:首个标记主节点主观下线的Sentinel会通过
SENTINEL is-master-down-by-addr
命令向其他Sentinel发起投票请求。 - 投票规则:需满足
quorum
值(如配置为2,则至少2个Sentinel投票确认主节点下线)。 - 防误判:多数哨兵达成共识可降低因网络问题导致的误判概率。
关键参数:quorum
在配置文件中通过sentinel monitor <master> <ip> <port> <quorum>
定义,通常建议为哨兵节点数/2 +1
。
选举领头哨兵(Leader Sentinel)
触发条件:主节点被标记为客观下线后,Sentinel集群需通过Raft算法选举一个领头哨兵来执行故障转移。
选举规则:
- 候选资格:仅发起客观下线投票的Sentinel可成为候选者。
- 投票机制:每个Sentinel仅能投一票(可投给自己或他人)。候选者需同时满足:获得半数以上Sentinel的投票和得票数≥
quorum
值。 - 超时重试:若选举失败,等待随机时间后重新发起投票。
is-master-down-by-addr命令说明:
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
- ip & port:目标主节点地址。
- current_epoch:当前配置纪元(用于防止旧消息干扰)。
- runid:
- runid=*:仅询问其他哨兵是否同意主节点下线(用于客观下线判定)。
- runid=哨兵自身ID:请求其他哨兵投票支持自己成为故障转移的领导者。
故障转移流程
执行步骤:
- 筛选从节点:剔除网络连接不稳定的从节点(如断连次数超过
down-after-milliseconds*10
阈值)。 - 打分规则(优先级排序):
- 第一轮:选择
slave-priority
配置值最小的从节点(优先级最高)。 - 第二轮:选择复制偏移量(
slave_repl_offset
)最大的从节点(数据最新)。 - 第三轮:选择运行ID(
runid
)最小的从节点(字典序最小)。
- 主从切换:
- 领头Sentinel向新主节点发送
slaveof no one
命令。 - 更新其他从节点指向新主节点,并通知客户端新拓扑信息。
主从切换成功后,sentinel会在+switch-master频道发发送主节点切换消息:
$ redis-cli -p 16381 subscribe +switch-master
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "+switch-master"
3) (integer) 1
1) "message"
2) "+switch-master"
3) "mymaster 127.0.0.1 6381 127.0.0.1 6382"
哨兵模式实战部署
Redis节点规划如下:
- Master: 127.0.0.1:6380
- Slave1: 127.0.0.1:6381(直连Master)
- Slave2: 127.0.0.1:6382(直连Master)
- Sentinel1:127.0.0.1:16380
- Sentinel2:127.0.0.1:16381
- Sentinel3:127.0.0.1:16382
在/test/目录分别新建/test/6380、/test/6381、/test/6382三个目录,用来存放redis的数据文件。然后在/test/目录分别新建/test/16380、/test/16381、/test/16382三个目录,用来存放sentinel的数据文件,这三个目录下都新建redis.conf文件,内容如下:
# /test/16380/redis.conf
port 16380
sentinel monitor mymaster 127.0.0.1 6380 2# /test/16381/redis.conf
port 16381
sentinel monitor mymaster 127.0.0.1 6380 2# /test/16382/redis.conf
port 16382
sentinel monitor mymaster 127.0.0.1 6380 2
三个sentinel数据目录下的文件内容都差不多,只有端口不一样,其中mymaster可以为任意的名字,一个sentinel可以对多个集群中的master节点进行监控,最后一个数字2的含义为3个节点中有2个节点(2票)判定master挂了就会开始自动故障转移。
先启动Master和2个Slave:
$ redis-server --dir /test/6380 --port 6380$ redis-server --dir /test/6381 --port 6381 --replicaof 127.0.0.1 6380$ redis-server --dir /test/6382 --port 6382 --replicaof 127.0.0.1 6380
然后启动三台sentinel(也可以使用单独的redis-sentinel命令):
$ redis-server /test/16380/redis.conf --sentinel$ redis-server /test/16381/redis.conf --sentinel$ redis-server /test/16382/redis.conf --sentinel
从Sentinel1的日志中能发现,它能根据Master节点找到2个Slave,以及其他两个Sentinel:
4922:X 16 Apr 2025 16:31:46.120 # Sentinel ID is ddb55a52514d296a3dca1123cfceaa309c134267
4922:X 16 Apr 2025 16:31:46.120 # +monitor master mymaster 127.0.0.1 6380 quorum 2
4922:X 16 Apr 2025 16:31:46.121 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
4922:X 16 Apr 2025 16:31:46.124 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
4922:X 16 Apr 2025 16:31:51.117 * +sentinel sentinel 9cc17be7b7e700d2800751113c5b0dad0944650f 127.0.0.1 16381 @ mymaster 127.0.0.1 6380
4922:X 16 Apr 2025 16:31:53.329 * +sentinel sentinel 950c560895d65451db8b65ebc0a418feef21ba82 127.0.0.1 16382 @ mymaster 127.0.0.1 6380
Sentinel可以通过向Master发送info命令来找到其他Slave:
$ redis-cli -p 6380 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=5399,lag=0
slave1:ip=127.0.0.1,port=6382,state=online,offset=5399,lag=0
master_failover_state:no-failover
master_replid:1eeb77e6f1d2ca889541314746796ccb6efa4404
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:5399
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:5399
Sentinel是怎么找到其他Sentinel的呢,原来他们都在主节点和所有的从节点上订阅了同一个频道,并将自己的信息发送到频道中,像一个聊天窗口一样,可以使用下面的命令来查看他们的聊天内容:
$ redis-cli -p 6380 subscribe __sentinel__:hello
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__sentinel__:hello"
3) (integer) 1
1) "message"
2) "__sentinel__:hello"
3) "127.0.0.1,16381,9cc17be7b7e700d2800751113c5b0dad0944650f,0,mymaster,127.0.0.1,6380,0"
1) "message"
2) "__sentinel__:hello"
3) "127.0.0.1,16382,950c560895d65451db8b65ebc0a418feef21ba82,0,mymaster,127.0.0.1,6380,0"
1) "message"
2) "__sentinel__:hello"
3) "127.0.0.1,16380,ddb55a52514d296a3dca1123cfceaa309c134267,0,mymaster,127.0.0.1,6380,0"
...
现在关闭Master节点,过了大概30s(由参数down-after-milliseconds决定),6381就称为了master节点,可以从Sentinel的日志中查看自动故障转移的大概过程。
Sentinel1:
4922:X 16 Apr 2025 16:34:02.955 # +sdown master mymaster 127.0.0.1 6380
4922:X 16 Apr 2025 16:34:03.043 # +new-epoch 1
4922:X 16 Apr 2025 16:34:03.051 # +vote-for-leader 950c560895d65451db8b65ebc0a418feef21ba82 1
4922:X 16 Apr 2025 16:34:03.680 # +config-update-from sentinel 950c560895d65451db8b65ebc0a418feef21ba82 127.0.0.1 16382 @ mymaster 127.0.0.1 6380
4922:X 16 Apr 2025 16:34:03.680 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
4922:X 16 Apr 2025 16:34:03.680 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
4922:X 16 Apr 2025 16:34:03.681 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
4922:X 16 Apr 2025 16:34:33.756 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
Sentinel2:
4927:X 16 Apr 2025 16:34:02.996 # +sdown master mymaster 127.0.0.1 6380
4927:X 16 Apr 2025 16:34:03.042 # +new-epoch 1
4927:X 16 Apr 2025 16:34:03.050 # +vote-for-leader 950c560895d65451db8b65ebc0a418feef21ba82 1
4927:X 16 Apr 2025 16:34:03.068 # +odown master mymaster 127.0.0.1 6380 #quorum 3/2
4927:X 16 Apr 2025 16:34:03.068 # Next failover delay: I will not start a failover before Wed Apr 16 16:40:03 2025
4927:X 16 Apr 2025 16:34:03.680 # +config-update-from sentinel 950c560895d65451db8b65ebc0a418feef21ba82 127.0.0.1 16382 @ mymaster 127.0.0.1 6380
4927:X 16 Apr 2025 16:34:03.680 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
4927:X 16 Apr 2025 16:34:03.680 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
4927:X 16 Apr 2025 16:34:03.680 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
4927:X 16 Apr 2025 16:34:33.739 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
Sentinel3:
4932:X 16 Apr 2025 16:34:02.959 # +sdown master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.022 # +odown master mymaster 127.0.0.1 6380 #quorum 2/2
4932:X 16 Apr 2025 16:34:03.022 # +new-epoch 1
4932:X 16 Apr 2025 16:34:03.022 # +try-failover master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.037 # +vote-for-leader 950c560895d65451db8b65ebc0a418feef21ba82 1
4932:X 16 Apr 2025 16:34:03.050 # 9cc17be7b7e700d2800751113c5b0dad0944650f voted for 950c560895d65451db8b65ebc0a418feef21ba82 1
4932:X 16 Apr 2025 16:34:03.052 # ddb55a52514d296a3dca1123cfceaa309c134267 voted for 950c560895d65451db8b65ebc0a418feef21ba82 1
4932:X 16 Apr 2025 16:34:03.104 # +elected-leader master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.105 # +failover-state-select-slave master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.158 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.158 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.216 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.625 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.625 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:03.680 * +slave-reconf-sent slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:04.173 # -odown master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:04.686 * +slave-reconf-inprog slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:04.686 * +slave-reconf-done slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:04.738 # +failover-end master mymaster 127.0.0.1 6380
4932:X 16 Apr 2025 16:34:04.738 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
4932:X 16 Apr 2025 16:34:04.738 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
4932:X 16 Apr 2025 16:34:04.738 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
4932:X 16 Apr 2025 16:34:34.752 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
再去看一下sentinel的数据目录的redis.conf文件的内容(sentinel会自动更新配置文件):
$ cat /test/16380/redis.conf
port 16380
sentinel monitor mymaster 127.0.0.1 6381 2
# Generated by CONFIG REWRITE
protected-mode no
user default on nopass ~* &* +@all
dir "/test"
sentinel myid ddb55a52514d296a3dca1123cfceaa309c134267
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
sentinel current-epoch 1
sentinel known-replica mymaster 127.0.0.1 6380
sentinel known-replica mymaster 127.0.0.1 6382
sentinel known-sentinel mymaster 127.0.0.1 16381 9cc17be7b7e700d2800751113c5b0dad0944650f
sentinel known-sentinel mymaster 127.0.0.1 16382 950c560895d65451db8b65ebc0a418feef21ba82
最后再启动原来关闭的Master节点,它会被sentinel监控到,然后成为6381的从节点。
关键配置参数说明
# 定义哨兵监控的主节点信息。
# quorum:触发主节点客观下线(ODOWN)所需的最小哨兵投票数。
# quorum通常设置为哨兵节点数的一半加一(如3哨兵时设为2),避免误判。
sentinel monitor <master-name> <ip> <port> <quorum># 定义节点无响应多久后被标记为主观下线(SDOWN)
sentinel down-after-milliseconds <master-name> <ms># 故障转移后,允许同时从新主节点同步数据的从节点数量。
sentinel parallel-syncs <master-name> <num># 定义故障转移各阶段的最大超时时间
sentinel failover-timeout <master-name> <ms># 设置连接主节点和从节点的密码
sentinel auth-pass <master-name> <password># 在NAT或Docker环境中,指定哨兵对外通信的IP和端口
sentinel announce-ip <ip>
sentinel announce-port <port> # 显式声明从节点地址,避免自动发现延迟
sentinel known-replica <master-name> <ip> <port># 定义故障事件触发的报警脚本路径
sentinel notification-script <master-name> <script-path>