Sentinel 是 Redis 的高可用性解决方案,它是一个分布式系统,用于监控一个或多个 Redis 主从实例,并在主实例发生故障时自动进行故障转移,将其中一个从实例提升为新的主实例。

对于 Python 应用程序来说,与 Sentinel 集群交互的核心目标是:
- 自动发现主节点:应用程序不应该硬编码连接某一个主节点,因为它的地址在故障转移后会改变。
- 自动获取从节点:如果需要读取,应该能自动获取可用的从节点。
- 处理连接中断:当发生故障转移时,旧的连接会失效,应用程序需要能自动重连到新的主节点。
Python 中最流行、功能最全的 Redis 客户端 redis-py 从版本 3.0 开始原生支持 Sentinel。
核心概念回顾
在开始代码之前,快速回顾一下 Sentinel 的三个核心角色:
master: 主节点,负责处理写请求。slave: 从节点,复制主节点的数据,负责处理读请求。sentinel: 哨兵节点,负责监控主从节点,并在主节点故障时执行故障转移。
环境准备
你需要安装 redis-py 库。

pip install redis
为了演示,你需要一个 Sentinel 集群环境,你可以轻松地使用 Docker Compose 来搭建一个本地环境。
docker-compose.yml
version: '3.7'
services:
redis-master:
image: redis:6.2-alpine
container_name: redis-master
command: redis-server --requirepass mymasterpassword
ports:
- "6379:6379"
redis-sentinel1:
image: redis:6.2-alpine
container_name: redis-sentinel1
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/usr/local/etc/redis/sentinel.conf
depends_on:
- redis-master
ports:
- "26379:26379"
redis-sentinel2:
image: redis:6.2-alpine
container_name: redis-sentinel2
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/usr/local/etc/redis/sentinel.conf
depends_on:
- redis-master
ports:
- "26380:26379"
redis-sentinel3:
image: redis:6.2-alpine
container_name: redis-sentinel3
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/usr/local/etc/redis/sentinel.conf
depends_on:
- redis-master
ports:
- "26381:26379"
sentinel1.conf, sentinel2.conf, sentinel3.conf (三个文件内容相同)
# 哨兵监控的主节点名称
# 格式: sentinel monitor <master-name> <ip> <port> <quorum>
# quorum 是哨兵节点判断主节点下线所需的最小票数
sentinel monitor mymaster redis-master 6379 2
# 设置主节点密码
sentinel auth-pass mymaster mymasterpassword
# 故障转移超时时间 (毫秒)
sentinel failover-timeout mymaster 30000
启动环境
docker-compose up -d
现在你拥有了一个包含 1 个主节点和 3 个哨兵节点的环境。
Python 连接 Sentinel 集群
redis-py 提供了 Sentinel 类来管理与哨兵的连接,它会自动从哨兵那里获取主节点和从节点的信息,并在连接失败时进行重试。
1 连接到 Sentinel 并获取主节点连接
这是最基本的使用方式。Sentinel 对象会连接到一组哨兵实例,master_for() 方法会向哨兵询问当前的主节点是谁,并返回一个连接到该主节点的 StrictRedis 客户端。
import redis
import time
# --- 配置 ---
# Sentinel 集群的地址列表
SENTINELS = [
('localhost', 26379),
('localhost', 26380),
('localhost', 26381)
]
# 被监控的主节点名称
MASTER_NAME = 'mymaster'
# Redis 密码
PASSWORD = 'mymasterpassword'
def main():
print("尝试连接到 Sentinel 集群...")
# 创建 Sentinel 客户端
# socket_timeout 设置哨兵连接的超时时间
sentinel = redis.sentinel.Sentinel(
SENTINELS,
socket_timeout=0.1,
password=PASSWORD
)
try:
# 获取主节点的连接
# socket_connect_timeout 设置连接到主节点的超时时间
# decode_responses=True 可以让 redis-py 自动将返回的 bytes 转为 str
master = sentinel.master_for(
MASTER_NAME,
socket_connect_timeout=1,
decode_responses=True
)
print("成功获取主节点连接。")
print(f"当前主节点地址: {master.connection_pool.connection_kwargs.get('host')}:{master.connection_pool.connection_kwargs.get('port')}")
# --- 测试写操作 ---
print("\n--- 测试写操作 ---")
master.set('foo', 'bar from master')
print(f"设置键 'foo' 的值为 'bar from master'")
# --- 测试读操作 ---
print("\n--- 测试读操作 ---")
value = master.get('foo')
print(f"从主节点读取键 'foo' 的值: {value}")
except redis.exceptions.ConnectionError as e:
print(f"连接错误: {e}")
except redis.exceptions.TimeoutError as e:
print(f"超时错误: {e}")
except Exception as e:
print(f"发生未知错误: {e}")
if __name__ == '__main__':
main()
运行结果:
尝试连接到 Sentinel 集群...
成功获取主节点连接。
当前主节点地址: localhost:6379
--- 测试写操作 ---
设置键 'foo' 的值为 'bar from master'
--- 测试读操作 ---
从主节点读取键 'foo' 的值: bar from master
2 获取从节点连接
如果你的应用需要实现读写分离,你可以使用 slave_for() 方法来获取一个从节点的连接。
# ... (Sentinel 配置部分与上面相同) ...
# 在 main() 函数中添加以下代码
try:
# ... (获取 master 连接的代码) ...
# --- 获取从节点连接 ---
print("\n--- 获取从节点连接 ---")
# min_other_slaves=1 表示至少要有1个健康的从节点才返回连接
slave = sentinel.slave_for(
MASTER_NAME,
socket_connect_timeout=1,
decode_responses=True,
min_other_slaves=1
)
print(f"成功获取从节点连接。")
print(f"当前从节点地址: {slave.connection_pool.connection_kwargs.get('host')}:{slave.connection_pool.connection_kwargs.get('port')}")
# --- 测试从节点的读操作 ---
print("\n--- 测试从节点的读操作 ---")
value_from_slave = slave.get('foo')
print(f"从从节点读取键 'foo' 的值: {value_from_slave}")
# ... (except 部分) ...
运行结果:
... (前面的输出) ...
--- 获取从节点连接 ---
成功获取从节点连接。
当前从节点地址: ... (某个从节点的IP和端口) ...
--- 测试从节点的读操作 ---
从从节点读取键 'foo' 的值: bar from master
故障转移演示
Sentinel 的最大价值在于其高可用性,下面我们来模拟主节点故障,看看 Python 客户端是否能自动恢复。
步骤 1: 运行 Python 客户端脚本
在你的终端运行上面的 Python 脚本,并让它持续运行。
python your_script_name.py
它会每隔几秒执行一次 get 操作,并打印出当前连接的节点信息。
步骤 2: 模拟主节点故障
在另一个终端,停止运行的主节点容器。
docker-compose stop redis-master
步骤 3: 观察现象
- Sentinel 集群:你会看到 Sentinel 集群的日志,它们会检测到
redis-master宕机,并开始投票,在quorum=2的情况下,很快会有两个哨兵节点同意将redis-master判为“客观下线”(ODOWN),然后启动故障转移流程,其中一个从节点会被提升为新的主节点。 - Python 客户端:
- 你会看到
your_script_name.py的输出中,get操作可能会先抛出一连串的ConnectionError或TimeoutError,这是因为它尝试连接到已经宕机的主节点。 redis-py的客户端是自动重试的,它会重新向 Sentinel 询问当前的主节点是谁。- 一旦 Sentinel 完成故障转移,并告诉客户端新的主节点地址,
redis-py会自动断开旧连接,并建立到新主节点的连接。 - 很快,你的脚本就会恢复正常的
get操作,并且打印出的主节点地址会变成新的那个被提升的从节点的地址。
- 你会看到
故障转移后的客户端输出示例:
... (正常输出) ...
当前主节点地址: localhost:6379
--- 测试读操作 ---
从主节点读取键 'foo' 的值: bar from master
# 此时你执行了 docker-compose stop redis-master
Traceback (most recent call last):
File "your_script_name.py", line X, in <module>
value = master.get('foo')
redis.exceptions.ConnectionError: Error while reading from socket: ('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer'))
# ... 一系列类似的错误 ...
# 然后客户端自动重连并恢复
... (重连日志) ...
成功获取主节点连接。
当前主节点地址: <新主节点的IP>:<新主节点的端口> <-- 注意这里地址变了
--- 测试读操作 ---
从主节点读取键 'foo' 的值: bar from master
这个自动重连和发现新主节点的机制,正是 Sentinel 集群对应用开发者透明和高可用的体现。
最佳实践和注意事项
- 配置多个哨兵地址:永远不要只配置一个哨兵的地址。
Sentinel类会维护一个哨兵列表,如果连接的第一个哨兵不可用,它会尝试列表中的下一个,直到找到一个可用的。 - 处理异常:即使有自动重试机制,你的业务代码也应该具备处理
redis.exceptions.RedisError的能力,在写操作失败后,可以实现重试逻辑或降级策略(比如返回错误或使用本地缓存)。 - 连接池:
master_for()和slave_for()返回的StrictRedis对象内部使用了连接池,这比每次操作都创建新连接要高效得多,确保你的应用程序生命周期内尽量复用这些客户端对象。 - 密码管理:在生产环境中,不要将密码硬编码在代码里,应该使用环境变量、配置文件或密钥管理服务来管理。
- 网络分区:在网络分区的情况下,可能会出现“脑裂”(Split-Brain)问题,即客户端认为主节点已下线,但实际上旧主节点仍在提供服务,Sentinel 通过
quorum机制来尽量避免这种情况,确保只有在大多数哨兵都认为主节点下线时才进行故障转移。
使用 Python 与 Sentinel 集群交互非常直接和强大。redis-py 的 Sentinel 类封装了所有复杂的发现和重连逻辑,使得开发者可以专注于业务逻辑,而无需担心底层 Redis 实例的故障和切换。
关键点就是:
- 使用
redis.sentinel.Sentinel连接到一组哨兵。 - 使用
sentinel.master_for()获取一个可自动重连的主节点客户端。 - 使用
sentinel.slave_for()获取一个可自动重连的从节点客户端。 - 编写健壮的代码来处理可能发生的网络异常。
