Redis 从入门到精通:Redis Sentinel 哨兵

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

上一篇我们搭建了 Redis 主从架构,实现了数据冗余和读写分离。但有个致命问题:当主节点宕机时,必须手动将某个从节点提升为主节点,再通知其他从节点和所有应用修改连接地址。这个过程少则几分钟,多则半小时,对于核心业务来说完全不可接受。

Redis Sentinel(哨兵) 就是为解决这个问题而生的。它能自动监控主从节点、自动发现故障、自动完成故障转移,让 Redis 真正具备生产级高可用能力。本文带你从原理到实战,用 Docker 搭建完整哨兵集群,并用 Python 客户端对接,感受自动故障转移的丝滑体验。

1. Sentinel 是什么?解决了什么问题?

Sentinel 是一套独立的分布式监控系统,它由多个哨兵进程组成,共同协作完成三个核心任务:

  • 监控(Monitoring):持续检查主节点和从节点是否正常运行。

  • 通知(Notification):当节点状态发生变化时,通过 API 或脚本通知管理员和其他应用。

  • 自动故障转移(Automatic Failover):当主节点不可用时,自动选举一个从节点升级为新主节点,并让其他从节点和新主节点同步。

架构图:

bash 复制代码
┌──────────────┬──────────────┬──────────────┐
│ Sentinel 1   │ Sentinel 2   │ Sentinel 3   │  ← 哨兵集群(至少 3 个)
└──────┬───────┴──────┬───────┴──────┬───────┘
       │              │              │
       ▼              ▼              ▼
┌──────────┐    ┌──────────┐    ┌──────────┐
│  Master  │───>│ Slave 1  │    │ Slave 2  │
└──────────┘    └──────────┘    └──────────┘

💡 Sentinel 本身不存储数据,它是独立于 Redis 数据节点的控制平面。即使没有 Sentinel,Redis 也能正常工作;加上 Sentinel,就拥有了自动化的"守护神"。

2. 核心原理:主观下线与客观下线

哨兵判断一个节点是否故障,用了两层机制。

2.1 主观下线(SDOWN)

每个哨兵进程每隔 1 秒向主节点、从节点、其他哨兵发送 PING 命令。如果某个节点在 down-after-milliseconds(配置项,默认 30 秒)时间内没有有效回复,这个哨兵自身 就会标记该节点为主观下线(Subjectively Down)。

但单个哨兵的判断可能失误------比如自己的网络出了故障。所以不能仅凭 SDOWN 就发起故障转移。

2.2 客观下线(ODOWN)

当某个哨兵发现主节点进入 SDOWN 后,它会询问其他哨兵:"你们也觉得主节点挂了吗?" 当达到 quorum(法定人数,通常为哨兵数量的半数以上)个哨兵都认为主节点下线,该节点就被标记为客观下线(Objectively Down)。

此时,哨兵集群会通过 Raft 算法 选举出一个哨兵领导者,由它来执行故障转移。

2.3 故障转移流程

  1. 哨兵领导者从所有健康的从节点中,按优先级、复制偏移量、runid 等规则选出最合适的一个。

  2. 向被选中的从节点发送 SLAVEOF NO ONE,将其提升为主节点。

  3. 向其他从节点发送 SLAVEOF 新主IP 新主端口,让它们同步新主。

  4. 将旧主节点的地址更新到哨兵配置中,一旦旧主恢复,它将成为新主的从节点。

整个过程通常在 10~30 秒内完成。

⚠️ Sentinel 只对主节点做客观下线判断。从节点和哨兵节点只需要主观下线即可做出反应。

3. 关键配置参数详解

在开始实战之前,先理解 Sentinel 的核心配置项:

bash 复制代码
# sentinel.conf 核心配置

# 监控的主节点:名称、IP、端口、quorum(法定人数)
sentinel monitor mymaster 127.0.0.1 6379 2

# 主观下线判定超时(毫秒),超过此时间无响应则 SDOWN
sentinel down-after-milliseconds mymaster 5000

# 故障转移最大超时(毫秒),超过此时间未完成则重新选举领导者
sentinel failover-timeout mymaster 10000

# 新主节点同步时,允许同时同步的从节点数量(越小同步越慢,但主节点压力小)
sentinel parallel-syncs mymaster 1

# 哨兵端口
port 26379

📌 quorum 建议 :3 个哨兵设 quorum=2;5 个哨兵设 quorum=3。这保证即使少数哨兵故障,也能正常完成故障转移。

4. 实战:Docker 搭建 Sentinel 集群

我们搭建一个"1 主 2 从 + 3 哨兵"的完整环境。

4.1 整体架构

bash 复制代码
Sentinel 集群(26379/26380/26381)
       │
       ▼
主节点 Master(6379)
       │
   ┌───┴───┐
   ▼       ▼
Slave1(6380) Slave2(6381)

4.2 创建网络和主从节点

bash 复制代码
# 创建网络
docker network create sentinel-net

# 启动主节点
docker run -d --name redis-master --network sentinel-net \
  -p 6379:6379 redis:7.2 redis-server --appendonly yes

# 启动从节点 1
docker run -d --name redis-slave1 --network sentinel-net \
  -p 6380:6379 redis:7.2 redis-server \
  --appendonly yes --slaveof redis-master 6379

# 启动从节点 2
docker run -d --name redis-slave2 --network sentinel-net \
  -p 6381:6379 redis:7.2 redis-server \
  --appendonly yes --slaveof redis-master 6379

验证主从状态:

bash 复制代码
docker exec redis-master redis-cli INFO replication | grep connected_slaves
# connected_slaves:2

4.3 配置并启动 3 个哨兵

首先在宿主机创建哨兵配置文件(3 份):

bash 复制代码
# 创建目录
mkdir -p ~/sentinel/conf

# 哨兵配置文件
cat > ~/sentinel/conf/sentinel.conf << 'EOF'
port 26379
dir /data
sentinel monitor mymaster redis-master 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
EOF

# 复制为 3 份(端口不同)
cp ~/sentinel/conf/sentinel.conf ~/sentinel/conf/sentinel1.conf
cp ~/sentinel/conf/sentinel.conf ~/sentinel/conf/sentinel2.conf
cp ~/sentinel/conf/sentinel.conf ~/sentinel/conf/sentinel3.conf

⚠️ 实际生产中 3 个哨兵最好部署在不同物理机上,这里仅演示原理,都放在同一台机器。

启动哨兵容器:

bash 复制代码
# 哨兵 1 (端口 26379)
docker run -d --name sentinel1 --network sentinel-net \
  -p 26379:26379 \
  -v ~/sentinel/conf/sentinel1.conf:/etc/redis/sentinel.conf \
  redis:7.2 redis-sentinel /etc/redis/sentinel.conf

# 哨兵 2 (端口 26380)
docker run -d --name sentinel2 --network sentinel-net \
  -p 26380:26379 \
  -v ~/sentinel/conf/sentinel2.conf:/etc/redis/sentinel.conf \
  redis:7.2 redis-sentinel /etc/redis/sentinel.conf

# 哨兵 3 (端口 26381)
docker run -d --name sentinel3 --network sentinel-net \
  -p 26381:26379 \
  -v ~/sentinel/conf/sentinel3.conf:/etc/redis/sentinel.conf \
  redis:7.2 redis-sentinel /etc/redis/sentinel.conf

4.4 验证哨兵状态

连接到任意哨兵查看信息:

bash 复制代码
docker exec -it sentinel1 redis-cli -p 26379

127.0.0.1:26379> SENTINEL masters
1)  1) "name"
    2) "mymaster"
    3) "ip"
    4) "redis-master"
    5) "port"
    6) "6379"
    7) "flags"
    8) "master"
    9) "num-slaves"
   10) "2"
   ...

127.0.0.1:26379> SENTINEL slaves mymaster
1)  1) "name"
    2) "172.18.0.3:6379"
    3) "ip"
    4) "172.18.0.3"
    ...

127.0.0.1:26379> SENTINEL sentinels mymaster
(列出所有监控 mymaster 的哨兵)

看到这些信息,说明 3 个哨兵已经成功发现主节点和从节点,系统处于健康监控状态。

5. 模拟故障转移:亲眼见证自动切换

5.1 停止主节点

等待约 5~10 秒(down-after-milliseconds 为 5000ms),观察哨兵日志:

bash 复制代码
docker logs sentinel1 --tail 20

关键日志行:

bash 复制代码
+sdown master mymaster redis-master 6379
+odown master mymaster redis-master 6379 #quorum 2/2
+try-failover master mymaster redis-master 6379
+vote-for-leader ...
+elected-leader master mymaster redis-master 6379
+failover-state-select-slave master mymaster redis-master 6379
+selected-slave slave 172.18.0.3:6379 ... redis-slave1 6379
+failover-state-send-slaveof-noone slave redis-slave1:6379
+failover-state-wait-promotion slave redis-slave1:6379
+promoted-slave slave redis-slave1:6379
+failover-state-reconf-slaves master mymaster ...
+slave-reconf-sent slave redis-slave2:6379
+failover-end master mymaster redis-master 6379
+switch-master mymaster redis-master 6379 172.18.0.3 6379

日志清晰地展示了完整过程:检测主观下线 → 达成客观下线共识 → 选举哨兵领导 → 选择最佳从节点 → 提升为新主 → 重配置其他从节点。

5.2 验证新主节点

bash 复制代码
# 查看新主状态
docker exec redis-slave1 redis-cli INFO replication | grep role
# role:master

# 查看原从节点 2 是否指向新主
docker exec redis-slave2 redis-cli INFO replication | grep master_host
# master_host:172.18.0.3  (即 redis-slave1)

原主节点恢复后会自动变成从节点:

bash 复制代码
docker start redis-master

# 几秒后
docker exec redis-master redis-cli INFO replication | grep role
# role:slave

6. Python 客户端对接 Sentinel

最关键的环节来了:应用如何自动感知主节点切换?答案是使用 Sentinel 感知的客户端。

6.1 安装 redis-py

6.2 Sentinel 连接方式

redis-py 提供了 Sentinel 类来管理主从发现:

bash 复制代码
from redis.sentinel import Sentinel
import time

# 配置所有哨兵地址
sentinel_hosts = [
    ('localhost', 26379),
    ('localhost', 26380),
    ('localhost', 26381),
]

# 创建 Sentinel 客户端
sentinel = Sentinel(sentinel_hosts, socket_timeout=0.5)

# 从哨兵获取当前主节点和从节点
master = sentinel.master_for('mymaster', socket_timeout=0.5, decode_responses=True)
slave = sentinel.slave_for('mymaster', socket_timeout=0.5, decode_responses=True)

# 写操作:走主节点
master.set('username', 'IT策士')
master.set('counter', 100)
print(f"写入完成: username={master.get('username')}, counter={master.get('counter')}")

# 读操作:走从节点
print(f"从节点读取: username={slave.get('username')}, counter={slave.get('counter')}")

# 获取当前主节点的实际地址
master_addr = sentinel.discover_master('mymaster')
print(f"当前主节点地址: {master_addr}")

slaves = sentinel.discover_slaves('mymaster')
print(f"当前从节点列表: {slaves}")

输出示例:

bash 复制代码
写入完成: username=IT策士, counter=100
从节点读取: username=IT策士, counter=100
当前主节点地址: ('172.18.0.3', 6379)
当前从节点列表: [('172.18.0.4', 6379), ('172.18.0.2', 6379)]

6.3 验证故障转移对客户端透明

编写一个持续读写的脚本来测试:

bash 复制代码
from redis.sentinel import Sentinel
import time

sentinel = Sentinel([
    ('localhost', 26379),
    ('localhost', 26380),
    ('localhost', 26381),
], socket_timeout=0.5)

master = sentinel.master_for('mymaster', socket_timeout=0.5, 
                              decode_responses=True, retry_on_timeout=True)

print("开始持续写入...")
for i in range(1000):
    try:
        key = f'key:{i % 10}'
        value = f'value:{i}'
        master.set(key, value)
        result = master.get(key)
        print(f"[{i:03d}] SET/GET {key} = {result}")
        time.sleep(0.1)
    except Exception as e:
        print(f"[{i:03d}] 错误: {e}")
        time.sleep(0.5)  # 等待故障转移完成

print("测试结束")

在脚本运行期间,用 docker stop redis-slave1 停掉当前主节点,观察输出:

bash 复制代码
[042] SET/GET key:2 = value:42
[043] SET/GET key:3 = value:43
[044] SET/GET key:4 = value:44
[045] 错误: Connection refused
[046] 错误: Connection refused
[047] SET/GET key:7 = value:47    ← 恢复!自动连上新主
[048] SET/GET key:8 = value:48

客户端在短暂报错后自动恢复,无需任何人工干预。这就是 Sentinel 的核心价值。

6.4 封装高可用客户端类

bash 复制代码
from redis.sentinel import Sentinel
from typing import List, Tuple

class RedisHA:
    """Sentinel 高可用 Redis 客户端"""

    def __init__(self, sentinel_hosts: List[Tuple[str, int]], service_name: str = 'mymaster',
                 socket_timeout: float = 0.5, decode_responses: bool = True):
        self.sentinel = Sentinel(sentinel_hosts, socket_timeout=socket_timeout)
        self.service_name = service_name
        self.decode_responses = decode_responses
        self._master = None
        self._slave = None

    @property
    def master(self):
        """获取主节点连接(自动发现)"""
        return self.sentinel.master_for(
            self.service_name, socket_timeout=0.5,
            decode_responses=self.decode_responses,
            retry_on_timeout=True
        )

    @property
    def slave(self):
        """获取从节点连接(轮询)"""
        return self.sentinel.slave_for(
            self.service_name, socket_timeout=0.5,
            decode_responses=self.decode_responses,
            retry_on_timeout=True
        )

    def get_master_addr(self):
        """获取当前主节点地址"""
        return self.sentinel.discover_master(self.service_name)

    def get_slaves(self):
        """获取所有从节点"""
        return self.sentinel.discover_slaves(self.service_name)

    def set(self, key, value, **kwargs):
        return self.master.set(key, value, **kwargs)

    def get(self, key):
        return self.slave.get(key)

    def delete(self, key):
        return self.master.delete(key)


# 使用
cache = RedisHA(
    sentinel_hosts=[('localhost', 26379), ('localhost', 26380), ('localhost', 26381)]
)

cache.set('app:version', '3.0')
print(cache.get('app:version'))
print(f'当前主节点: {cache.get_master_addr()}')

7. 生产环境最佳实践

  • 哨兵数量:至少 3 个,且部署在不同物理机或可用区。偶数个哨兵会增加投票平局的概率。

  • quorum 设置sentinel_num / 2 + 1,例如 3 哨兵设 2,5 哨兵设 3。

  • 客户端超时和重试 :设置 socket_timeoutretry_on_timeout,避免因网络抖动抛异常。

  • 监控哨兵本身 :哨兵也可能挂,需要监控哨兵进程的存活和 SENTINEL masters 的正确性。

  • 避免在故障转移期间操作:故障转移一般 10~30 秒,此期间短暂不可写入是正常现象。

  • 不要将哨兵和数据节点混布在同一台机器:否则机器宕机时数据节点和哨兵一起挂。

8. 动手试试

  1. 模拟主节点宕机 :在 Python 持续写入时,docker stop 主节点,观察恢复时间。

  2. 模拟哨兵宕机:停掉 1 个哨兵,观察剩下 2 个是否仍能正常完成故障转移(quorum=2)。

  3. 查看故障转移后配置 :检查哨兵配置文件,看 sentinel monitor 行是否自动更新为新主地址。

  4. 网络分区模拟:通过 Docker 网络隔离主节点,观察哨兵的行为和客户端恢复。

预期效果:主节点宕机后 10~20 秒自动恢复写入;单哨兵宕机不影响集群可用性;配置文件自动更新;网络分区恢复后旧主变为从。

9. 总结

Sentinel 让 Redis 从"单点可用"跃升为"真正的自动高可用"。但它无法解决写的横向扩展------所有写入仍然只能走一个主节点。下一篇,我们将迎来 Redis 的终极形态:Redis Cluster,用哈希槽实现数据分片,彻底突破单机瓶颈。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
XovH1 小时前
Redis 从入门到精通:分片之道 —— Redis Cluster
后端
用户938515635071 小时前
从零实现一个 Todos 应用:原生 Ajax + Node 服务,顺便吃透 JSON.stringify
前端·javascript·后端
霸道流氓气质1 小时前
Spring Boot 文件上传大小限制配置全解析
spring boot·后端·firefox
Java面试题总结1 小时前
SpringBoot API参数校验
java·spring boot·后端
何以解忧,唯有..1 小时前
Go 语言安装与环境配置完整指南
开发语言·后端·golang
alwaysrun1 小时前
C++之常量体系const
c++·后端·程序员
武子康1 小时前
Java-24 深入浅出 Spring 全景:从起源到 Spring 6 一文打通 IoC / AOP / 发展史
java·后端·spring
zyk_computer1 小时前
AI Agent ,让循环收敛的那套闭环控制系统
人工智能·后端·python·ai·架构·agent·ai agent
地铁潜行者2 小时前
消息堆积后,为什么一扩容消费者,MySQL 先被打崩了?
java·后端