Redis 传输协议精讲 / 09 - 哨兵协议
哨兵协议
9.1 Sentinel 概述
Redis Sentinel 是 Redis 的高可用方案,提供:
| 功能 | 说明 |
|---|---|
| 监控(Monitoring) | 持续检查主从节点是否正常工作 |
| 通知(Notification) | 通过 Pub/Sub 通知客户端故障事件 |
| 自动故障转移(Automatic Failover) | 主节点不可用时自动选举新主节点 |
| 配置提供(Configuration Provider) | 客户端通过 Sentinel 获取当前主节点地址 |
架构
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sentinel │ │ Sentinel │ │ Sentinel │
│ 1 │ │ 2 │ │ 3 │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────────────┼──────────────┘
│ 监控
┌─────────┴─────────┐
│ │
┌────┴────┐ ┌────┴────┐
│ Master │ ──复制──→│ Slave │
└─────────┘ └─────────┘
9.2 Sentinel 通信协议
Sentinel 本身是一个特殊的 Redis 实例,使用标准的 Redis 协议进行通信。
Sentinel 端口
# 默认端口 26379(不是 6379)
redis-sentinel /path/to/sentinel.conf
# 或
redis-server /path/to/sentinel.conf --sentinel
Sentinel 之间的通信
Sentinel 之间通过 Pub/Sub 频道交换信息:
# Sentinel 订阅的频道
__sentinel__:hello
每个 Sentinel 定期向该频道发布消息,包含:
- Sentinel 自身的信息(IP、端口、ID)
- 主节点的信息(IP、端口、ID、配置纪元)
→ PUBLISH __sentinel__:hello "<sentinel-ip>,<sentinel-port>,<sentinel-runid>,<sentinel-epoch>,<master-name>,<master-ip>,<master-port>,<master-epoch>"
9.3 SENTINEL 命令
SENTINEL masters
获取所有被监控的主节点信息:
→ SENTINEL masters
← *1 ← 一个主节点
← %16 ← 16 个字段
← $4
← name
← $6
← mymaster
← $2
← ip
← $9
← 127.0.0.1
← $4
← port
← $4
← 6379
← ...
SENTINEL master
获取特定主节点的详细信息:
→ SENTINEL master mymaster
← %16
← $2
← ip
← $9
← 127.0.0.1
← $4
← port
← $4
← 6379
← $6
← runid
← $40
← <40字符的运行ID>
← ...
SENTINEL replicas
获取从节点信息:
→ SENTINEL replicas mymaster
← *1 ← 一个从节点
← %14
← $2
← ip
← $9
← 127.0.0.1
← $4
← port
← $4
← 6380
← ...
SENTINEL sentinels
获取其他 Sentinel 的信息:
→ SENTINEL sentinels mymaster
← *2 ← 两个其他 Sentinel
← %6
← $2
← ip
← $9
← 127.0.0.1
← $4
← port
← $4
← 26380
← ...
SENTINEL get-master-addr-by-name
获取主节点地址(最常用的命令):
→ SENTINEL get-master-addr-by-name mymaster
← *2
← $9
← 127.0.0.1
← $4
← 6379
9.4 健康检查机制
主观下线(SDOWN)
单个 Sentinel 认为主节点不可达:
# 配置(sentinel.conf)
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
| 参数 | 说明 |
|---|---|
mymaster | 主节点名称 |
127.0.0.1 6379 | 主节点地址 |
2 | 法定人数(quorum),至少需要几个 Sentinel 同意 |
down-after-milliseconds | 多少毫秒无响应判定为 SDOWN |
客观下线(ODOWN)
当 quorum 个 Sentinel 都认为主节点下线时,标记为客观下线:
SDOWN(单个 Sentinel)
↓ quorum 个 Sentinel 同意
ODOWN(客观下线)
↓ 触发故障转移
Failover(故障转移)
检测命令
# 查询主节点状态
redis-cli -p 26379 SENTINEL master mymaster
# 关键字段:
# flags: master / s_down / o_down
# num-other-sentinels: 其他 Sentinel 数量
9.5 故障转移流程
步骤详解
1. 主节点 SDOWN → 多个 Sentinel 确认 → ODOWN
2. Sentinel Leader 选举(Raft 协议)
3. Leader 选择最优从节点
4. Leader 向从节点发送 SLAVEOF NO ONE
5. 等待从节点晋升为主节点
6. 通知其他从节点复制新主节点
7. 通知客户端新主节点地址
从节点选择算法
1. 排除 SDOWN 或 ODOWN 的从节点
2. 排除最近 5 秒内无响应的从节点
3. 排除与旧主节点断开时间超过 (down-after-milliseconds * 10) 的从节点
4. 按优先级(slave-priority)排序
5. 优先级相同,按复制偏移量排序(数据最新的优先)
6. 偏移量相同,按 runid 排序(字典序最小的优先)
故障转移期间的协议交互
# Sentinel → 从节点
→ SLAVEOF NO ONE
← +OK
# Sentinel → 其他从节点
→ SLAVEOF <new-master-ip> <new-master-port>
← +OK
# Sentinel → 旧主节点(恢复后)
→ SLAVEOF <new-master-ip> <new-master-port>
← +OK
9.6 Sentinel Pub/Sub 通知
频道列表
Sentinel 通过 Pub/Sub 频道广播事件:
| 频道 | 事件 |
|---|---|
+sdown | 主观下线 |
-sdown | 恢复在线 |
+odown | 客观下线 |
-odown | 恢复在线 |
+failover-state-reconf-slaves | 故障转移:重新配置从节点 |
+failover-detected | 检测到故障转移 |
+slave | 发现新从节点 |
+switch-master | 主节点切换(最重要) |
+sentinel | 发现新 Sentinel |
订阅方式
# 订阅所有事件
→ SUBSCRIBE *
← *3
← $9
← subscribe
← $1
← *
← :1
# 订阅特定主节点的事件
→ SUBSCRIBE +switch-master
消息格式
*3
$14
+switch-master
$8
mymaster
$26
127.0.0.1 6379 127.0.0.1 6380
消息内容格式:<master-name> <old-ip> <old-port> <new-ip> <new-port>
9.7 客户端集成
自动发现主节点
import redis
from redis.sentinel import Sentinel
# 创建 Sentinel 实例
sentinel = Sentinel(
[("127.0.0.1", 26379),
("127.0.0.1", 26380),
("127.0.0.1", 26381)],
socket_timeout=0.5
)
# 获取主节点连接
master = sentinel.master_for("mymaster", socket_timeout=0.5)
master.set("key", "value")
# 获取从节点连接(只读)
slave = sentinel.slave_for("mymaster", socket_timeout=0.5)
value = slave.get("key")
手动实现 Sentinel 客户端
class SimpleSentinelClient:
def __init__(self, sentinel_addrs):
self.sentinel_addrs = sentinel_addrs
self.master_addr = None
def discover_master(self, master_name):
"""通过 Sentinel 发现主节点"""
for host, port in self.sentinel_addrs:
try:
s = socket.create_connection((host, port), timeout=2)
cmd = f"*3\r\n$8\r\nSENTINEL\r\n$28\r\nget-master-addr-by-name\r\n${len(master_name)}\r\n{master_name}\r\n"
s.sendall(cmd.encode())
# 读取响应(简化处理)
resp = s.recv(4096).decode()
# 解析两元素数组:[ip, port]
lines = resp.split("\r\n")
ip = lines[4] # 跳过数组头和长度
port = int(lines[6])
s.close()
return (ip, port)
except:
continue
raise ConnectionError("All sentinels unreachable")
def get_master(self, master_name):
"""获取主节点连接"""
if self.master_addr is None:
self.master_addr = self.discover_master(master_name)
host, port = self.master_addr
return socket.create_connection((host, port))
自动故障转移处理
import redis
import time
class ResilientRedisClient:
"""支持自动故障转移的 Redis 客户端"""
def __init__(self, sentinel_addrs, master_name):
self.sentinel = Sentinel(sentinel_addrs)
self.master_name = master_name
self._master = None
@property
def master(self):
if self._master is None:
self._master = self.sentinel.master_for(
self.master_name,
socket_timeout=2,
retry_on_timeout=True
)
return self._master
def execute_with_retry(self, func, max_retries=3):
"""执行命令,失败时自动重试"""
for attempt in range(max_retries):
try:
return func(self.master)
except (redis.ConnectionError, redis.TimeoutError):
# 连接失败,可能发生了故障转移
self._master = None # 清除缓存,下次重新发现
if attempt < max_retries - 1:
time.sleep(0.5 * (attempt + 1))
raise redis.ConnectionError("All retries failed")
def get(self, key):
return self.execute_with_retry(lambda r: r.get(key))
def set(self, key, value, **kwargs):
return self.execute_with_retry(lambda r: r.set(key, value, **kwargs))
# 使用
client = ResilientRedisClient(
[("127.0.0.1", 26379), ("127.0.0.1", 26380), ("127.0.0.1", 26381)],
"mymaster"
)
client.set("key", "value")
print(client.get("key"))
9.8 Sentinel 配置管理
动态配置
# 添加新的主节点监控
SENTINEL MONITOR newmaster 127.0.0.1 6381 2
# 移除监控
SENTINEL REMOVE newmaster
# 修改配置参数
SENTINEL SET mymaster down-after-milliseconds 10000
SENTINEL SET mymaster failover-timeout 60000
配置持久化
Sentinel 会自动将运行时配置写入配置文件:
# sentinel.conf(自动生成的内容)
sentinel myid abc123...
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-replica mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 26380 other-sentinel-id
9.9 Sentinel 集群拓扑
推荐部署
最少 3 个 Sentinel(奇数个,用于选举)
分布在不同的物理机器/可用区
Node 1: Redis Master (6379) + Sentinel (26379)
Node 2: Redis Slave (6379) + Sentinel (26379)
Node 3: Redis Slave (6379) + Sentinel (26379)
法定人数(Quorum)
# 法定人数 = 需要多少个 Sentinel 同意才能判定 ODOWN
sentinel monitor mymaster 127.0.0.1 6379 2
# 3 个 Sentinel 中有 2 个同意 → ODOWN
# 建议:quorum = (num_sentinels / 2) + 1
9.10 注意事项
⚠️ Sentinel 不是代理 客户端通过 Sentinel 获取主节点地址后,直接连接 Redis 实例。Sentinel 不转发数据。
⚠️ 网络分区 在网络分区场景下,可能出现脑裂(split-brain)。配置
min-replicas-to-write可以缓解。
⚠️ 配置纪元(Configuration Epoch) Sentinel 使用 Raft 协议选举 Leader,使用纪元号确保配置一致性。纪元号越大表示配置越新。
⚠️ 客户端缓存 客户端应缓存主节点地址,并在连接失败时重新查询 Sentinel。不要每次都查询 Sentinel。
9.11 扩展阅读
| 资源 | 说明 |
|---|---|
| Redis Sentinel 文档 | 官方文档 |
| Sentinel 客户端指引 | 客户端实现指南 |
| Raft 共识算法 | Sentinel Leader 选举的基础 |