杰瑞科技汇

Python Sentinel集群如何搭建与配置?

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

Python Sentinel集群如何搭建与配置?-图1
(图片来源网络,侵删)

对于 Python 应用程序来说,与 Sentinel 集群交互的核心目标是:

  1. 自动发现主节点:应用程序不应该硬编码连接某一个主节点,因为它的地址在故障转移后会改变。
  2. 自动获取从节点:如果需要读取,应该能自动获取可用的从节点。
  3. 处理连接中断:当发生故障转移时,旧的连接会失效,应用程序需要能自动重连到新的主节点。

Python 中最流行、功能最全的 Redis 客户端 redis-py 从版本 3.0 开始原生支持 Sentinel。


核心概念回顾

在开始代码之前,快速回顾一下 Sentinel 的三个核心角色:

  • master: 主节点,负责处理写请求。
  • slave: 从节点,复制主节点的数据,负责处理读请求。
  • sentinel: 哨兵节点,负责监控主从节点,并在主节点故障时执行故障转移。

环境准备

你需要安装 redis-py 库。

Python Sentinel集群如何搭建与配置?-图2
(图片来源网络,侵删)
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: 观察现象

  1. Sentinel 集群:你会看到 Sentinel 集群的日志,它们会检测到 redis-master 宕机,并开始投票,在 quorum=2 的情况下,很快会有两个哨兵节点同意将 redis-master 判为“客观下线”(ODOWN),然后启动故障转移流程,其中一个从节点会被提升为新的主节点。
  2. Python 客户端
    • 你会看到 your_script_name.py 的输出中,get 操作可能会先抛出一连串的 ConnectionErrorTimeoutError,这是因为它尝试连接到已经宕机的主节点。
    • 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 集群对应用开发者透明和高可用的体现。


最佳实践和注意事项

  1. 配置多个哨兵地址:永远不要只配置一个哨兵的地址。Sentinel 类会维护一个哨兵列表,如果连接的第一个哨兵不可用,它会尝试列表中的下一个,直到找到一个可用的。
  2. 处理异常:即使有自动重试机制,你的业务代码也应该具备处理 redis.exceptions.RedisError 的能力,在写操作失败后,可以实现重试逻辑或降级策略(比如返回错误或使用本地缓存)。
  3. 连接池master_for()slave_for() 返回的 StrictRedis 对象内部使用了连接池,这比每次操作都创建新连接要高效得多,确保你的应用程序生命周期内尽量复用这些客户端对象。
  4. 密码管理:在生产环境中,不要将密码硬编码在代码里,应该使用环境变量、配置文件或密钥管理服务来管理。
  5. 网络分区:在网络分区的情况下,可能会出现“脑裂”(Split-Brain)问题,即客户端认为主节点已下线,但实际上旧主节点仍在提供服务,Sentinel 通过 quorum 机制来尽量避免这种情况,确保只有在大多数哨兵都认为主节点下线时才进行故障转移。

使用 Python 与 Sentinel 集群交互非常直接和强大。redis-pySentinel 类封装了所有复杂的发现和重连逻辑,使得开发者可以专注于业务逻辑,而无需担心底层 Redis 实例的故障和切换。

关键点就是:

  1. 使用 redis.sentinel.Sentinel 连接到一组哨兵。
  2. 使用 sentinel.master_for() 获取一个可自动重连的主节点客户端。
  3. 使用 sentinel.slave_for() 获取一个可自动重连的从节点客户端。
  4. 编写健壮的代码来处理可能发生的网络异常。
分享:
扫描分享到社交APP
上一篇
下一篇