Redis 从入门到精通:Redis Sentinel 哨兵
IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。
上一篇我们搭建了 Redis 主从架构,实现了数据冗余和读写分离。但有个致命问题:当主节点宕机时,必须手动将某个从节点提升为主节点,再通知其他从节点和所有应用修改连接地址。这个过程少则几分钟,多则半小时,对于核心业务来说完全不可接受。
Redis Sentinel(哨兵)就是为解决这个问题而生的。它能自动监控主从节点、自动发现故障、自动完成故障转移,让 Redis 真正具备生产级高可用能力。本文带你从原理到实战,用 Docker 搭建完整哨兵集群,并用 Python 客户端对接,感受自动故障转移的丝滑体验。
1. Sentinel 是什么?解决了什么问题?
Sentinel 是一套独立的分布式监控系统,它由多个哨兵进程组成,共同协作完成三个核心任务:
监控(Monitoring):持续检查主节点和从节点是否正常运行。
通知(Notification):当节点状态发生变化时,通过 API 或脚本通知管理员和其他应用。
自动故障转移(Automatic Failover):当主节点不可用时,自动选举一个从节点升级为新主节点,并让其他从节点和新主节点同步。
架构图:
┌──────────────┬──────────────┬──────────────┐ │ Sentinel1│ Sentinel2│ Sentinel3│ ← 哨兵集群(至少3个) └──────┬───────┴──────┬───────┴──────┬───────┘ │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Master │───>│ Slave1│ │ Slave2│ └──────────┘ └──────────┘ └──────────┘💡 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 故障转移流程
哨兵领导者从所有健康的从节点中,按优先级、复制偏移量、runid 等规则选出最合适的一个。
向被选中的从节点发送
SLAVEOF NO ONE,将其提升为主节点。向其他从节点发送
SLAVEOF 新主IP 新主端口,让它们同步新主。将旧主节点的地址更新到哨兵配置中,一旦旧主恢复,它将成为新主的从节点。
整个过程通常在10~30 秒内完成。
⚠️ Sentinel 只对主节点做客观下线判断。从节点和哨兵节点只需要主观下线即可做出反应。
3. 关键配置参数详解
在开始实战之前,先理解 Sentinel 的核心配置项:
# sentinel.conf 核心配置# 监控的主节点:名称、IP、端口、quorum(法定人数)sentinel monitor mymaster127.0.0.163792# 主观下线判定超时(毫秒),超过此时间无响应则 SDOWNsentinel down-after-milliseconds mymaster5000# 故障转移最大超时(毫秒),超过此时间未完成则重新选举领导者sentinel failover-timeout mymaster10000# 新主节点同步时,允许同时同步的从节点数量(越小同步越慢,但主节点压力小)sentinel parallel-syncs mymaster1# 哨兵端口port26379📌quorum 建议:3 个哨兵设
quorum=2;5 个哨兵设quorum=3。这保证即使少数哨兵故障,也能正常完成故障转移。
4. 实战:Docker 搭建 Sentinel 集群
我们搭建一个“1 主 2 从 + 3 哨兵”的完整环境。
4.1 整体架构
Sentinel 集群(26379/26380/26381) │ ▼ 主节点 Master(6379) │ ┌───┴───┐ ▼ ▼ Slave1(6380) Slave2(6381)4.2 创建网络和主从节点
# 创建网络dockernetwork create sentinel-net# 启动主节点dockerrun-d--nameredis-master--networksentinel-net\-p6379:6379 redis:7.2 redis-server--appendonlyyes# 启动从节点 1dockerrun-d--nameredis-slave1--networksentinel-net\-p6380:6379 redis:7.2 redis-server\--appendonlyyes--slaveofredis-master6379# 启动从节点 2dockerrun-d--nameredis-slave2--networksentinel-net\-p6381:6379 redis:7.2 redis-server\--appendonlyyes--slaveofredis-master6379验证主从状态:
dockerexecredis-master redis-cli INFO replication|grepconnected_slaves# connected_slaves:24.3 配置并启动 3 个哨兵
首先在宿主机创建哨兵配置文件(3 份):
# 创建目录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.confcp~/sentinel/conf/sentinel.conf ~/sentinel/conf/sentinel2.confcp~/sentinel/conf/sentinel.conf ~/sentinel/conf/sentinel3.conf⚠️ 实际生产中 3 个哨兵最好部署在不同物理机上,这里仅演示原理,都放在同一台机器。
启动哨兵容器:
# 哨兵 1 (端口 26379)dockerrun-d--namesentinel1--networksentinel-net\-p26379:26379\-v~/sentinel/conf/sentinel1.conf:/etc/redis/sentinel.conf\redis:7.2 redis-sentinel /etc/redis/sentinel.conf# 哨兵 2 (端口 26380)dockerrun-d--namesentinel2--networksentinel-net\-p26380:26379\-v~/sentinel/conf/sentinel2.conf:/etc/redis/sentinel.conf\redis:7.2 redis-sentinel /etc/redis/sentinel.conf# 哨兵 3 (端口 26381)dockerrun-d--namesentinel3--networksentinel-net\-p26381:26379\-v~/sentinel/conf/sentinel3.conf:/etc/redis/sentinel.conf\redis:7.2 redis-sentinel /etc/redis/sentinel.conf4.4 验证哨兵状态
连接到任意哨兵查看信息:
dockerexec-itsentinel1 redis-cli-p26379127.0.0.1:26379>SENTINEL masters1)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 mymaster1)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),观察哨兵日志:
dockerlogs sentinel1--tail20关键日志行:
+sdown master mymaster redis-master6379+odown master mymaster redis-master6379#quorum 2/2+try-failover master mymaster redis-master6379+vote-for-leader... +elected-leader master mymaster redis-master6379+failover-state-select-slave master mymaster redis-master6379+selected-slave slave172.18.0.3:6379... redis-slave16379+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-master6379+switch-master mymaster redis-master6379172.18.0.36379日志清晰地展示了完整过程:检测主观下线 → 达成客观下线共识 → 选举哨兵领导 → 选择最佳从节点 → 提升为新主 → 重配置其他从节点。
5.2 验证新主节点
# 查看新主状态dockerexecredis-slave1 redis-cli INFO replication|greprole# role:master# 查看原从节点 2 是否指向新主dockerexecredis-slave2 redis-cli INFO replication|grepmaster_host# master_host:172.18.0.3 (即 redis-slave1)原主节点恢复后会自动变成从节点:
dockerstart redis-master# 几秒后dockerexecredis-master redis-cli INFO replication|greprole# role:slave6. Python 客户端对接 Sentinel
最关键的环节来了:应用如何自动感知主节点切换?答案是使用 Sentinel 感知的客户端。
6.1 安装 redis-py
6.2 Sentinel 连接方式
redis-py提供了Sentinel类来管理主从发现:
from redis.sentinelimportSentinelimporttime# 配置所有哨兵地址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}")输出示例:
写入完成: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 验证故障转移对客户端透明
编写一个持续读写的脚本来测试:
from redis.sentinelimportSentinelimporttimesentinel=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("开始持续写入...")foriinrange(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停掉当前主节点,观察输出:
[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 封装高可用客户端类
from redis.sentinelimportSentinel from typingimportList, 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):"""获取主节点连接(自动发现)"""returnself.sentinel.master_for(self.service_name,socket_timeout=0.5,decode_responses=self.decode_responses,retry_on_timeout=True)@property def slave(self):"""获取从节点连接(轮询)"""returnself.sentinel.slave_for(self.service_name,socket_timeout=0.5,decode_responses=self.decode_responses,retry_on_timeout=True)def get_master_addr(self):"""获取当前主节点地址"""returnself.sentinel.discover_master(self.service_name)def get_slaves(self):"""获取所有从节点"""returnself.sentinel.discover_slaves(self.service_name)def set(self, key, value, **kwargs):returnself.master.set(key, value, **kwargs)def get(self, key):returnself.slave.get(key)def delete(self, key):returnself.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_timeout和retry_on_timeout,避免因网络抖动抛异常。监控哨兵本身:哨兵也可能挂,需要监控哨兵进程的存活和
SENTINEL masters的正确性。避免在故障转移期间操作:故障转移一般 10~30 秒,此期间短暂不可写入是正常现象。
不要将哨兵和数据节点混布在同一台机器:否则机器宕机时数据节点和哨兵一起挂。
8. 动手试试
模拟主节点宕机:在 Python 持续写入时,
docker stop主节点,观察恢复时间。模拟哨兵宕机:停掉 1 个哨兵,观察剩下 2 个是否仍能正常完成故障转移(quorum=2)。
查看故障转移后配置:检查哨兵配置文件,看
sentinel monitor行是否自动更新为新主地址。网络分区模拟:通过 Docker 网络隔离主节点,观察哨兵的行为和客户端恢复。
预期效果:主节点宕机后 10~20 秒自动恢复写入;单哨兵宕机不影响集群可用性;配置文件自动更新;网络分区恢复后旧主变为从。
9. 总结
Sentinel 让 Redis 从“单点可用”跃升为“真正的自动高可用”。但它无法解决写的横向扩展——所有写入仍然只能走一个主节点。下一篇,我们将迎来 Redis 的终极形态:Redis Cluster,用哈希槽实现数据分片,彻底突破单机瓶颈。
想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !
