艾体宝干货 | Redis Python 开发系列#5 高可用与集群部署指南

本文详细讲解 Redis 主从复制、哨兵模式和集群模式的原理与配置,提供完整的 Python 连接代码示例,确保你的应用在生产环境中实现高可用性和可扩展性。

前言

在掌握了 Redis 的所有核心数据结构和高级特性后,我们面临最后一个关键挑战:如何让 Redis 在生产环境中**不宕机**?单点 Redis 实例一旦故障,将导致整个应用不可用。这就是 Redis 高可用(High Availability)集群(Cluster) 架构要解决的根本问题。

本篇读者收益​:

  • 深入理解 Redis **主从复制**、**哨兵模式(Sentinel)** 和 集群模式(Cluster) 的架构原理。
  • 掌握使用 redis-py 连接 Redis Sentinel 实现自动故障转移和读写分离。
  • 掌握使用 redis-pyredis-cluster 连接 Redis Cluster 实现数据分片和水平扩展。
  • 了解云服务(如 AWS ElastiCache、Azure Cache)的连接要点。

先修要求​:已掌握 Redis 基础连接和操作(详见系列前四篇)。

关键要点​:

  1. 主从复制:数据冗余的基础,从节点提供读扩展,但不具备自动故障转移能力。
  2. 哨兵模式(Sentinel) :在复制基础上增加了监控、通知和自动故障转移,实现真正的高可用。
  3. 集群模式(Cluster) :通过数据分片(sharding)实现水平扩展,兼具高可用和可扩展性。
  4. 从单机到哨兵再到集群,是一个在**复杂度、可用性和扩展性**之间的权衡过程。

背景与原理简述

随着业务增长,单机 Redis 会遇到两个核心瓶颈:

  1. 可用性瓶颈:单个节点故障导致服务完全中断。
  2. 性能/容量瓶颈:单机内存、CPU、网络带宽有限。

Redis 提供了三种进阶部署方案来解决这些问题:

  • **主从复制(Replication)**:一个主节点(master)负责写操作,多个从节点(slave)复制主节点数据并提供读服务。解决了**读扩展**和**数据备份**,但没有自动故障转移。
  • **哨兵模式(Sentinel)**:在复制基础上,引入专门的哨兵进程来监控节点健康状态,并在主节点故障时**自动选举新的主节点**,实现高可用。
  • **集群模式(Cluster)**:将数据自动分片到多个主节点上,每个主节点都有对应的从节点。同时解决了**水平扩展**和**高可用**问题。

环境准备与快速上手

安装必要的 Python 库

Bash 复制代码
# 安装 redis-py(支持 Sentinel 和基础 Cluster 连接)
pip install "redis[hiredis]"

# 对于更完整的 Cluster 支持,推荐安装 redis-py-cluster
pip install redis-py-cluster

基础连接测试

Python 复制代码
# filename: setup.py
import os
import redis
from redis.sentinel import Sentinel
from redis.cluster import RedisCluster
from redis.exceptions import RedisError, ConnectionError

print("Redis 高可用与集群连接演示环境就绪")

核心用法与代码示例

主从复制(Replication)

架构概述

  • 一个主节点(可写可读)
  • 一个或多个从节点(只读,异步复制主节点数据)
  • 客户端手动处理读写分离

Python 连接示例

Python 复制代码
# filename: replication_demo.py
def replication_setup():
    """演示主从复制的基本连接(手动读写分离)"""
    
    # 连接主节点(写操作)
    master = redis.Redis(
        host='redis-master-host',
        port=6379,
        password='your_password',
        decode_responses=True
    )
    
    # 连接从节点(读操作)
    slave = redis.Redis(
        host='redis-slave-host', 
        port=6379,
        password='your_password',
        decode_responses=True
    )
    
    # 写入只能在主节点
    master.set('global:counter', 100)
    
    # 读取可以在从节点(注意复制延迟)
    value = slave.get('global:counter')
    print(f"从节点读取的值: {value}")
    
    return master, slave

# 注意:生产环境不建议手动管理主从,推荐使用 Sentinel

哨兵模式(Sentinel)

架构概述

  • 多个哨兵进程(Sentinel)组成集群,监控 Redis 节点
  • 自动故障检测和主节点选举
  • 客户端通过哨兵发现当前的主节点

Python 连接 Sentinel

Python 复制代码
# filename: sentinel_demo.py
def sentinel_connection():
    """连接 Redis Sentinel 集群"""
    
    # 1. 定义哨兵节点列表
    sentinel_nodes = [
        ('sentinel1.example.com', 26379),
        ('sentinel2.example.com', 26379), 
        ('sentinel3.example.com', 26379),
    ]
    
    # 2. 创建 Sentinel 对象
    sentinel = Sentinel(
        sentinel_nodes,
        socket_timeout=0.1,
        password='your_sentinel_password',  # 如果哨兵有密码
        decode_responses=True
    )
    
    # 3. 获取主节点和从节点客户端
    # service_name 是在哨兵配置中定义的集群名称
    master = sentinel.master_for(
        'my-redis-cluster',  # service_name
        socket_timeout=0.1,
        password='your_redis_password',
        decode_responses=True
    )
    
    slave = sentinel.slave_for(
        'my-redis-cluster',  # service_name  
        socket_timeout=0.1,
        password='your_redis_password',
        decode_responses=True
    )
    
    return master, slave, sentinel

def sentinel_operations():
    """使用 Sentinel 客户端进行操作"""
    try:
        master, slave, sentinel = sentinel_connection()
        
        # 写入操作 - 使用主节点
        master.set('sentinel:test', 'hello from master')
        master.incr('sentinel:counter')
        
        # 读取操作 - 使用从节点
        value = slave.get('sentinel:test')
        counter = slave.get('sentinel:counter')
        print(f"从节点读取: {value}, 计数器: {counter}")
        
        # 发现当前主从节点信息
        current_master = sentinel.discover_master('my-redis-cluster')
        current_slaves = sentinel.discover_slaves('my-redis-cluster')
        print(f"当前主节点: {current_master}")
        print(f"当前从节点: {current_slaves}")
        
    except RedisError as e:
        print(f"Sentinel 操作失败: {e}")

# 运行示例
sentinel_operations()

哨兵故障转移演示

Python 复制代码
# filename: sentinel_failover.py
import time
import threading

def monitor_sentinel_status():
    """监控哨兵状态(模拟故障转移观察)"""
    sentinel = Sentinel([
        ('localhost', 26379),
        ('localhost', 26380), 
        ('localhost', 26381),
    ], socket_timeout=0.5)
    
    while True:
        try:
            master_addr = sentinel.discover_master('my-redis-cluster')
            slaves = sentinel.discover_slaves('my-redis-cluster')
            print(f"[{time.strftime('%H:%M:%S')}] 主节点: {master_addr}, 从节点: {slaves}")
        except RedisError as e:
            print(f"监控错误: {e}")
        
        time.sleep(2)

def sentinel_auto_failover_test():
    """测试哨兵的自动故障转移"""
    master, slave, sentinel = sentinel_connection()
    
    # 持续写入数据
    def writer():
        counter = 0
        while True:
            try:
                master.set('failover:test', f'value_{counter}')
                master.incr('failover:counter')
                counter += 1
                time.sleep(1)
            except RedisError as e:
                print(f"写入失败: {e}, 等待故障转移...")
                time.sleep(3)
    
    # 持续读取数据  
    def reader():
        while True:
            try:
                value = slave.get('failover:test')
                counter = slave.get('failover:counter')
                print(f"读取: {value}, 计数器: {counter}")
            except RedisError as e:
                print(f"读取失败: {e}")
            time.sleep(1)
    
    # 启动读写线程
    threading.Thread(target=writer, daemon=True).start()
    threading.Thread(target=reader, daemon=True).start()
    
    # 运行监控
    monitor_sentinel_status()

# 注意:运行此示例需要真实的哨兵环境
# sentinel_auto_failover_test()

集群模式(Cluster)

架构概述

  • 数据自动分片到 16384 个槽位(slots)
  • 每个节点负责一部分槽位
  • 客户端直接路由命令到正确的节点
  • 每个主节点都有对应的从节点

Python 连接 Cluster

Python 复制代码
# filename: cluster_demo.py
def cluster_connection():
    """连接 Redis Cluster"""
    
    # 启动节点列表(不需要所有节点,能连接一个即可发现整个集群)
    startup_nodes = [
        {"host": "redis-cluster-node1.example.com", "port": 6379},
        {"host": "redis-cluster-node2.example.com", "port": 6379},
        {"host": "redis-cluster-node3.example.com", "port": 6379},
    ]
    
    try:
        # 方法1: 使用 redis-py-cluster (推荐)
        from redis.cluster import RedisCluster
        
        rc = RedisCluster(
            startup_nodes=startup_nodes,
            password='your_cluster_password',
            decode_responses=True,
            skip_full_coverage_check=True,  # 避免不必要的全集群检查
            socket_connect_timeout=5,
            socket_timeout=5,
            retry_on_timeout=True,
            max_connections_per_node=20
        )
        
        return rc
        
    except ImportError:
        # 方法2: 使用 redis-py 内置的集群支持(基础功能)
        print("redis-py-cluster 未安装,使用 redis-py 基础集群支持")
        rc = redis.Redis(
            host=startup_nodes[0]['host'],
            port=startup_nodes[0]['port'],
            password='your_cluster_password',
            decode_responses=True
        )
        
        return rc

def cluster_operations():
    """集群基本操作"""
    try:
        rc = cluster_connection()
        
        # 基本操作 - API 与单机版基本一致
        rc.set('cluster:key1', 'value1')
        rc.set('cluster:key2', 'value2')
        
        value1 = rc.get('cluster:key1')
        value2 = rc.get('cluster:key2')
        print(f"获取值: key1={value1}, key2={value2}")
        
        # 计数器操作
        rc.incr('cluster:counter')
        counter = rc.get('cluster:counter')
        print(f"计数器: {counter}")
        
        # 获取集群信息
        if hasattr(rc, 'cluster_info'):
            info = rc.cluster_info()
            print(f"集群状态: {info.get('cluster_state')}")
            print(f"已知节点数: {info.get('cluster_known_nodes')}")
        
        # 获取键所在的槽位和节点
        key_slot = rc.cluster_keyslot('cluster:key1')
        print(f"key1 的槽位: {key_slot}")
        
    except RedisError as e:
        print(f"集群操作失败: {e}")

# 运行示例
cluster_operations()

集群分片与路由

Python 复制代码
# filename: cluster_sharding.py
def cluster_sharding_demo():
    """演示集群的数据分片特性"""
    rc = cluster_connection()
    
    # 存储多个键,它们会被自动分配到不同节点
    keys = []
    for i in range(10):
        key = f'sharding:key:{i}'
        rc.set(key, f'value_{i}')
        keys.append(key)
    
    # 验证键分布在不同的槽位
    for key in keys:
        slot = rc.cluster_keyslot(key)
        # 获取负责该槽位的节点
        node = rc.nodes_manager.get_node_from_slot(slot)
        print(f"键 {key} -> 槽位 {slot} -> 节点 {node}")
    
    # 批量操作的限制:跨槽位的 MSET/MGET 会失败
    try:
        # 这可能会失败,因为 keys 可能在不同的槽位
        result = rc.mget(keys)
        print(f"批量获取成功: {result}")
    except redis.RedisError as e:
        print(f"跨槽位批量操作失败: {e}")
        # 解决方案:使用 pipeline 或哈希标签确保键在同一个槽位

def cluster_hash_tags():
    """使用哈希标签确保相关键在同一个槽位"""
    rc = cluster_connection()
    
    # 使用 {user123} 作为哈希标签,确保所有 user123 相关的键在同一个槽位
    user_id = "user123"
    
    rc.set(f"user:{{{user_id}}}:profile", "profile_data")
    rc.set(f"user:{{{user_id}}}:session", "session_data") 
    rc.set(f"user:{{{user_id}}}:preferences", "prefs_data")
    
    # 现在可以安全地进行批量操作
    keys = [
        f"user:{{{user_id}}}:profile",
        f"user:{{{user_id}}}:session", 
        f"user:{{{user_id}}}:preferences"
    ]
    
    try:
        values = rc.mget(keys)
        print(f"使用哈希标签批量获取: {values}")
    except redis.RedisError as e:
        print(f"哈希标签批量操作失败: {e}")

cluster_hash_tags()

性能优化与容量规划

连接池配置实践

Python 复制代码
# filename: production_connection_pools.py
def production_connection_configs():
    """生产环境连接配置示例"""
    
    # Sentinel 连接池配置
    sentinel_pool = redis.sentinel.SentinelConnectionPool(
        'my-redis-cluster',
        redis.sentinel.Sentinel([
            ('sentinel1', 26379),
            ('sentinel2', 26379),
            ('sentinel3', 26379),
        ]),
        password='your_password',
        max_connections=50,
        socket_timeout=5,
        socket_connect_timeout=5,
        retry_on_timeout=True,
        health_check_interval=30
    )
    
    # Cluster 连接池配置
    cluster_pool = RedisCluster(
        startup_nodes=[{"host": "node1", "port": 6379}],
        password='your_password',
        max_connections=100,  # 每个节点的最大连接数
        socket_connect_timeout=5,
        socket_timeout=5,
        retry_on_timeout=True,
        reinitialize_steps=10  # 每10次命令后重新初始化连接
    )

监控与健康检查

Python 复制代码
# filename: health_monitoring.py
class RedisHealthMonitor:
    """Redis 健康监控器"""
    
    def __init__(self, client):
        self.client = client
    
    def check_connectivity(self):
        """检查基本连通性"""
        try:
            return self.client.ping()
        except (ConnectionError, RedisError):
            return False
    
    def get_info(self, section=None):
        """获取 Redis 信息"""
        try:
            if hasattr(self.client, 'info'):
                return self.client.info(section)
            else:
                # Cluster 的特殊处理
                return self.client.cluster_info()
        except RedisError:
            return None
    
    def monitor_memory_usage(self):
        """监控内存使用情况"""
        info = self.get_info('memory')
        if info:
            return {
                'used_memory': info.get('used_memory_human', 'N/A'),
                'used_memory_peak': info.get('used_memory_peak_human', 'N/A'),
                'memory_fragmentation_ratio': info.get('mem_fragmentation_ratio', 'N/A')
            }
        return None
    
    def check_replication_status(self):
        """检查复制状态(适用于主从和集群)"""
        info = self.get_info('replication')
        if info:
            role = info.get('role')
            if role == 'master':
                return {
                    'role': 'master',
                    'connected_slaves': info.get('connected_slaves', 0)
                }
            elif role == 'slave':
                return {
                    'role': 'slave', 
                    'master_link_status': info.get('master_link_status', 'down'),
                    'master_host': info.get('master_host', 'unknown')
                }
        return None

安全与可靠性

  • 使用安全组/防火墙限制访问来源
  • Redis 节点部署在私有子网
  • 哨兵和集群节点间使用专用网络
  • 配置适当的 save 规则或启用 AOF
  • 定期测试备份恢复流程
  • 跨可用区部署提高容灾能力

案例

云服务连接示例(AWS ElastiCache)

Python 复制代码
# filename: aws_elasticache.py
def connect_aws_elasticache():
    """连接 AWS ElastiCache Redis"""
    
    # ElastiCache Redis (Cluster Mode Disabled) - 使用 Sentinel
    if os.getenv('REDIS_MODE') == 'sentinel':
        sentinel = Sentinel([
            ('primary-endpoint', 26379),
            ('secondary-endpoint', 26379),
        ], socket_timeout=1)
        
        client = sentinel.master_for(
            'my-cluster',
            socket_timeout=1,
            password=os.getenv('REDIS_PASSWORD')
        )
    
    # ElastiCache Redis (Cluster Mode Enabled) - 使用 Cluster
    elif os.getenv('REDIS_MODE') == 'cluster':
        # 获取配置端点
        configuration_endpoint = os.getenv('REDIS_CLUSTER_CONFIG_ENDPOINT')
        host, port = configuration_endpoint.split(':')
        
        client = RedisCluster(
            startup_nodes=[{"host": host, "port": int(port)}],
            password=os.getenv('REDIS_PASSWORD'),
            skip_full_coverage_check=True,
            decode_responses=True
        )
    
    else:
        # 单机模式
        client = redis.Redis(
            host=os.getenv('REDIS_HOST'),
            port=int(os.getenv('REDIS_PORT')),
            password=os.getenv('REDIS_PASSWORD'),
            decode_responses=True
        )
    
    return client

# 生产环境配置管理器
class RedisConnectionManager:
    """生产环境 Redis 连接管理器"""
    
    _clients = {}
    
    @classmethod
    def get_client(cls, service_name='default'):
        """获取 Redis 客户端(单例模式)"""
        if service_name not in cls._clients:
            if os.getenv('REDIS_CLUSTER_ENABLED') == 'true':
                cls._clients[service_name] = connect_aws_elasticache()
            else:
                # 其他连接逻辑
                pass
        
        # 健康检查
        try:
            cls._clients[service_name].ping()
        except RedisError:
            # 重新建立连接
            cls._clients[service_name] = connect_aws_elasticache()
        
        return cls._clients[service_name]

小结

从单机到哨兵再到集群,Redis 提供了完整的解决方案来满足不同规模应用的高可用和可扩展性需求。哨兵模式为读写分离和自动故障转移提供了优雅的解决方案,而集群模式则通过数据分片实现了真正的水平扩展。

相关推荐
3***89191 小时前
开放自己本机的mysql允许别人连接
数据库·mysql·adb
X***C8621 小时前
使用bitnamiredis-sentinel部署Redis 哨兵模式
数据库·redis·sentinel
f***01931 小时前
CC++链接数据库(MySQL)超级详细指南
c语言·数据库·c++
q***23571 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
W***95241 小时前
在Spring Boot项目中使用MySQL数据库
数据库·spring boot·mysql
合方圆~小文1 小时前
球型摄像机作为现代监控系统的核心设备
java·数据库·c++·人工智能
q***87602 小时前
yum安装redis
数据库·redis·缓存
煎蛋学姐3 小时前
SSM旅游资讯信息服务系统的实现04s3n(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·旅游·ssm 框架·旅游资讯系统·会员管理
3***31213 小时前
初识MySQL · 库的操作
数据库·mysql