[python]python操作redis集群(实战)

2,336 阅读10分钟

我来为你提供一个完整的 Python 操作 Redis 集群的示例。

1. 安装必要的库

pip install redis-py-cluster

2. 完整的 Redis 集群操作示例

#!/usr/bin/env python3
"""
Redis 集群操作完整示例
"""

from rediscluster import RedisCluster
import json
import time
from datetime import datetime


class RedisClusterManager:
    def __init__(self, startup_nodes, password=None):
        """
        初始化 Redis 集群连接
        
        Args:
            startup_nodes: Redis 集群节点列表
            password: 密码(如果有)
        """
        # Redis 集群连接参数
        self.connection_kwargs = {
            'decode_responses': True,  # 自动解码,返回字符串而不是字节
            'skip_full_coverage_check': True,  # 跳过完整覆盖检查
        }
        
        if password:
            self.connection_kwargs['password'] = password
        
        # 创建 Redis 集群连接
        try:
            self.redis_cluster = RedisCluster(
                startup_nodes=startup_nodes,
                **self.connection_kwargs
            )
            print("✅ Redis 集群连接成功!")
            
            # 获取集群信息
            self.print_cluster_info()
            
        except Exception as e:
            print(f"❌ 连接 Redis 集群失败: {e}")
            raise
    
    def print_cluster_info(self):
        """打印集群信息"""
        try:
            # 获取集群信息
            cluster_info = self.redis_cluster.info()
            
            print("\n=== Redis 集群信息 ===")
            print(f"集群状态: {cluster_info.get('cluster_state', '未知')}")
            print(f"已知节点数: {cluster_info.get('cluster_known_nodes', '未知')}")
            print(f"槽位分配状态: {cluster_info.get('cluster_slots_assigned', '未知')}")
            print(f"槽位正常状态: {cluster_info.get('cluster_slots_ok', '未知')}")
            
        except Exception as e:
            print(f"获取集群信息失败: {e}")
    
    def basic_operations(self):
        """基本键值操作示例"""
        print("\n=== 基本键值操作 ===")
        
        try:
            # 1. 设置字符串值
            self.redis_cluster.set("user:1001:name", "张三")
            print("✅ 设置字符串值成功")
            
            # 2. 获取字符串值
            name = self.redis_cluster.get("user:1001:name")
            print(f"获取值: user:1001:name = {name}")
            
            # 3. 设置带过期时间的值
            self.redis_cluster.setex("session:abc123", 300, "session_data")
            print("✅ 设置带过期时间的值成功")
            
            # 4. 检查键是否存在
            exists = self.redis_cluster.exists("user:1001:name")
            print(f"键 user:1001:name 是否存在: {exists}")
            
            # 5. 删除键
            self.redis_cluster.delete("user:1001:name")
            exists_after = self.redis_cluster.exists("user:1001:name")
            print(f"删除后键是否存在: {exists_after}")
            
        except Exception as e:
            print(f"❌ 基本操作失败: {e}")
    
    def hash_operations(self):
        """哈希操作示例"""
        print("\n=== 哈希操作 ===")
        
        try:
            # 1. 设置哈希字段
            user_data = {
                "id": "1001",
                "name": "李四",
                "age": "25",
                "email": "lisi@example.com"
            }
            self.redis_cluster.hset("user:1001", mapping=user_data)
            print("✅ 设置哈希成功")
            
            # 2. 获取所有字段
            all_fields = self.redis_cluster.hgetall("user:1001")
            print(f"用户信息: {all_fields}")
            
            # 3. 获取特定字段
            name = self.redis_cluster.hget("user:1001", "name")
            print(f"用户名: {name}")
            
            # 4. 设置多个字段
            self.redis_cluster.hmset("user:1001", {"city": "北京", "job": "工程师"})
            
            # 5. 删除哈希字段
            self.redis_cluster.hdel("user:1001", "job")
            
        except Exception as e:
            print(f"❌ 哈希操作失败: {e}")
    
    def list_operations(self):
        """列表操作示例"""
        print("\n=== 列表操作 ===")
        
        try:
            list_key = "task:queue"
            
            # 1. 从右侧推入元素
            for i in range(5):
                self.redis_cluster.rpush(list_key, f"task_{i}")
            print("✅ 推入列表元素成功")
            
            # 2. 获取列表长度
            length = self.redis_cluster.llen(list_key)
            print(f"列表长度: {length}")
            
            # 3. 获取列表所有元素
            all_tasks = self.redis_cluster.lrange(list_key, 0, -1)
            print(f"所有任务: {all_tasks}")
            
            # 4. 从左侧弹出元素
            first_task = self.redis_cluster.lpop(list_key)
            print(f"弹出的第一个任务: {first_task}")
            
            # 5. 清空列表
            self.redis_cluster.delete(list_key)
            
        except Exception as e:
            print(f"❌ 列表操作失败: {e}")
    
    def set_operations(self):
        """集合操作示例"""
        print("\n=== 集合操作 ===")
        
        try:
            set_key = "user:tags:1001"
            
            # 1. 添加元素到集合
            tags = ["python", "redis", "database", "backend"]
            for tag in tags:
                self.redis_cluster.sadd(set_key, tag)
            print("✅ 添加集合元素成功")
            
            # 2. 获取集合所有成员
            all_tags = self.redis_cluster.smembers(set_key)
            print(f"所有标签: {all_tags}")
            
            # 3. 检查元素是否存在
            has_python = self.redis_cluster.sismember(set_key, "python")
            print(f"是否包含 'python': {has_python}")
            
            # 4. 获取集合大小
            size = self.redis_cluster.scard(set_key)
            print(f"集合大小: {size}")
            
            # 5. 移除元素
            self.redis_cluster.srem(set_key, "database")
            
        except Exception as e:
            print(f"❌ 集合操作失败: {e}")
    
    def sorted_set_operations(self):
        """有序集合操作示例"""
        print("\n=== 有序集合操作 ===")
        
        try:
            zset_key = "game:scores"
            
            # 1. 添加带分数的成员
            scores = {
                "player1": 1500,
                "player2": 1800,
                "player3": 1200,
                "player4": 2000
            }
            self.redis_cluster.zadd(zset_key, scores)
            print("✅ 添加有序集合成功")
            
            # 2. 按分数排序获取成员
            top_players = self.redis_cluster.zrevrange(zset_key, 0, 2, withscores=True)
            print("排行榜前三:")
            for player, score in top_players:
                print(f"  {player}: {score}")
            
            # 3. 获取成员的排名
            player2_rank = self.redis_cluster.zrevrank(zset_key, "player2")
            print(f"player2 的排名: {player2_rank}")
            
            # 4. 获取分数范围内的成员
            high_scores = self.redis_cluster.zrangebyscore(
                zset_key, min=1500, max=2500, withscores=True
            )
            print("高分玩家:")
            for player, score in high_scores:
                print(f"  {player}: {score}")
                
        except Exception as e:
            print(f"❌ 有序集合操作失败: {e}")
    
    def json_operations(self):
        """JSON 数据操作示例"""
        print("\n=== JSON 数据操作 ===")
        
        try:
            # 创建复杂 JSON 数据
            product_data = {
                "id": "P001",
                "name": "智能手机",
                "price": 2999.99,
                "stock": 100,
                "specs": {
                    "brand": "华为",
                    "model": "Mate 50",
                    "color": ["黑色", "白色", "蓝色"]
                },
                "tags": ["手机", "智能", "5G"],
                "timestamp": datetime.now().isoformat()
            }
            
            # 存储 JSON 数据
            json_key = "product:P001"
            self.redis_cluster.set(json_key, json.dumps(product_data, ensure_ascii=False))
            print("✅ 存储 JSON 数据成功")
            
            # 读取 JSON 数据
            json_str = self.redis_cluster.get(json_key)
            if json_str:
                loaded_data = json.loads(json_str)
                print(f"产品名称: {loaded_data['name']}")
                print(f"产品价格: {loaded_data['price']}")
                print(f"产品品牌: {loaded_data['specs']['brand']}")
            
        except Exception as e:
            print(f"❌ JSON 操作失败: {e}")
    
    def pipeline_operations(self):
        """管道操作示例(批量操作)"""
        print("\n=== 管道操作 ===")
        
        try:
            # 创建管道
            pipeline = self.redis_cluster.pipeline()
            
            # 批量添加操作
            start_time = time.time()
            
            for i in range(100, 110):
                pipeline.set(f"temp:key:{i}", f"value_{i}")
                pipeline.expire(f"temp:key:{i}", 60)  # 60秒后过期
            
            # 执行所有操作
            results = pipeline.execute()
            
            end_time = time.time()
            print(f"✅ 管道操作完成,执行了 {len(results)} 个命令")
            print(f"耗时: {end_time - start_time:.3f} 秒")
            
        except Exception as e:
            print(f"❌ 管道操作失败: {e}")
    
    def transaction_operations(self):
        """事务操作示例"""
        print("\n=== 事务操作 ===")
        
        try:
            # 注意:Redis 集群的事务限制在单个节点上
            # 我们需要确保所有操作在同一个槽位
            
            # 使用相同的槽位键
            key1 = "account:1001:balance"
            key2 = "account:1001:history"
            
            # 设置初始值
            self.redis_cluster.set(key1, 1000)
            
            # 创建事务
            pipeline = self.redis_cluster.pipeline()
            
            # 开始事务(MULTI)
            pipeline.multi()
            
            # 在事务中执行多个操作
            pipeline.decrby(key1, 200)  # 扣除 200
            pipeline.rpush(key2, f"支出:200,时间:{datetime.now().isoformat()}")
            pipeline.incrby(key1, 50)   # 增加 50
            
            # 执行事务(EXEC)
            results = pipeline.execute()
            
            print(f"✅ 事务执行成功,结果: {results}")
            
            # 查看最终余额
            final_balance = self.redis_cluster.get(key1)
            print(f"最终余额: {final_balance}")
            
        except Exception as e:
            print(f"❌ 事务操作失败: {e}")
    
    def scan_operations(self):
        """SCAN 操作示例(遍历键)"""
        print("\n=== SCAN 操作 ===")
        
        try:
            # 插入一些测试数据
            for i in range(20):
                self.redis_cluster.set(f"test:key:{i}", f"value_{i}")
            
            # 使用 SCAN 遍历键
            cursor = 0
            pattern = "test:key:*"
            count = 5
            all_keys = []
            
            print(f"使用模式 '{pattern}' 扫描键:")
            
            while True:
                cursor, keys = self.redis_cluster.scan(
                    cursor=cursor,
                    match=pattern,
                    count=count
                )
                
                if keys:
                    all_keys.extend(keys)
                    print(f"  找到键: {keys}")
                
                if cursor == 0:
                    break
            
            print(f"总共找到 {len(all_keys)} 个匹配的键")
            
            # 清理测试数据
            for key in all_keys:
                self.redis_cluster.delete(key)
                
        except Exception as e:
            print(f"❌ SCAN 操作失败: {e}")
    
    def test_connection(self):
        """测试连接和基本功能"""
        print("\n=== 连接测试 ===")
        
        try:
            # 简单的 ping 测试
            result = self.redis_cluster.ping()
            print(f"Ping 响应: {result}")
            
            # 设置测试键值
            test_key = "test:connection"
            test_value = f"测试数据 {datetime.now().isoformat()}"
            
            self.redis_cluster.setex(test_key, 10, test_value)
            retrieved = self.redis_cluster.get(test_key)
            
            print(f"设置的值: {test_value}")
            print(f"获取的值: {retrieved}")
            
            if test_value == retrieved:
                print("✅ 连接测试通过")
            else:
                print("❌ 连接测试失败")
                
        except Exception as e:
            print(f"❌ 连接测试失败: {e}")
    
    def close(self):
        """关闭连接"""
        try:
            self.redis_cluster.close()
            print("✅ Redis 集群连接已关闭")
        except:
            pass


def main():
    """
    主函数 - 演示 Redis 集群操作
    """
    # Redis 集群节点配置
    # 根据你的实际集群配置修改这些节点
    startup_nodes = [
        {"host": "127.0.0.1", "port": 7000},
        {"host": "127.0.0.1", "port": 7001},
        {"host": "127.0.0.1", "port": 7002},
        # 可以添加更多节点
    ]
    
    # 如果有密码,在这里设置
    password = None  # 或 "your_password"
    
    # 创建 Redis 集群管理器实例
    redis_manager = None
    
    try:
        # 初始化连接
        redis_manager = RedisClusterManager(startup_nodes, password)
        
        # 运行各种操作示例
        redis_manager.test_connection()           # 测试连接
        redis_manager.basic_operations()          # 基本操作
        redis_manager.hash_operations()           # 哈希操作
        redis_manager.list_operations()           # 列表操作
        redis_manager.set_operations()            # 集合操作
        redis_manager.sorted_set_operations()     # 有序集合操作
        redis_manager.json_operations()           # JSON 操作
        redis_manager.pipeline_operations()       # 管道操作
        redis_manager.transaction_operations()    # 事务操作
        redis_manager.scan_operations()           # SCAN 操作
        
        print("\n🎉 所有操作示例完成!")
        
    except Exception as e:
        print(f"❌ 程序执行出错: {e}")
        
    finally:
        # 确保连接被关闭
        if redis_manager:
            redis_manager.close()


if __name__ == "__main__":
    main()

3. 简单的生产环境示例

#!/usr/bin/env python3
"""
生产环境 Redis 集群工具类
"""

from rediscluster import RedisCluster
import logging
from typing import Any, Optional, Dict, List
import json


class RedisClusterClient:
    def __init__(self, nodes: List[Dict], password: Optional[str] = None):
        """
        初始化 Redis 集群客户端
        
        Args:
            nodes: Redis 集群节点列表 [{"host": "127.0.0.1", "port": 7000}, ...]
            password: Redis 密码
        """
        self.logger = logging.getLogger(__name__)
        
        # 连接配置
        self.connection_pool_kwargs = {
            'max_connections': 50,
            'decode_responses': True,
        }
        
        if password:
            self.connection_pool_kwargs['password'] = password
        
        try:
            self.client = RedisCluster(
                startup_nodes=nodes,
                skip_full_coverage_check=True,
                socket_keepalive=True,
                **self.connection_pool_kwargs
            )
            self.logger.info("Redis 集群连接成功")
        except Exception as e:
            self.logger.error(f"Redis 集群连接失败: {e}")
            raise
    
    # ========== 通用操作 ==========
    
    def set(self, key: str, value: Any, expire: Optional[int] = None) -> bool:
        """设置键值"""
        try:
            if expire:
                return self.client.setex(key, expire, value)
            else:
                return self.client.set(key, value)
        except Exception as e:
            self.logger.error(f"设置键值失败 {key}: {e}")
            return False
    
    def get(self, key: str) -> Optional[str]:
        """获取键值"""
        try:
            return self.client.get(key)
        except Exception as e:
            self.logger.error(f"获取键值失败 {key}: {e}")
            return None
    
    def delete(self, *keys) -> int:
        """删除键"""
        try:
            return self.client.delete(*keys)
        except Exception as e:
            self.logger.error(f"删除键失败 {keys}: {e}")
            return 0
    
    def exists(self, key: str) -> bool:
        """检查键是否存在"""
        try:
            return bool(self.client.exists(key))
        except Exception as e:
            self.logger.error(f"检查键存在失败 {key}: {e}")
            return False
    
    def expire(self, key: str, seconds: int) -> bool:
        """设置过期时间"""
        try:
            return bool(self.client.expire(key, seconds))
        except Exception as e:
            self.logger.error(f"设置过期时间失败 {key}: {e}")
            return False
    
    # ========== JSON 操作 ==========
    
    def set_json(self, key: str, value: Any, expire: Optional[int] = None) -> bool:
        """存储 JSON 数据"""
        try:
            json_str = json.dumps(value, ensure_ascii=False)
            return self.set(key, json_str, expire)
        except Exception as e:
            self.logger.error(f"存储 JSON 失败 {key}: {e}")
            return False
    
    def get_json(self, key: str) -> Optional[Any]:
        """获取 JSON 数据"""
        try:
            json_str = self.get(key)
            if json_str:
                return json.loads(json_str)
            return None
        except Exception as e:
            self.logger.error(f"获取 JSON 失败 {key}: {e}")
            return None
    
    # ========== 哈希操作 ==========
    
    def hset(self, key: str, mapping: Dict) -> bool:
        """设置哈希字段"""
        try:
            return bool(self.client.hset(key, mapping=mapping))
        except Exception as e:
            self.logger.error(f"设置哈希失败 {key}: {e}")
            return False
    
    def hgetall(self, key: str) -> Dict:
        """获取所有哈希字段"""
        try:
            return self.client.hgetall(key)
        except Exception as e:
            self.logger.error(f"获取哈希失败 {key}: {e}")
            return {}
    
    # ========== 分布式锁 ==========
    
    def acquire_lock(self, lock_key: str, timeout: int = 10) -> bool:
        """获取分布式锁"""
        import uuid
        identifier = str(uuid.uuid4())
        
        try:
            # 使用 SET NX EX 命令获取锁
            result = self.client.set(
                lock_key,
                identifier,
                ex=timeout,
                nx=True
            )
            return result is True
        except Exception as e:
            self.logger.error(f"获取锁失败 {lock_key}: {e}")
            return False
    
    def release_lock(self, lock_key: str, identifier: str) -> bool:
        """释放分布式锁"""
        try:
            # 使用 Lua 脚本确保原子性释放锁
            lua_script = """
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
            """
            result = self.client.eval(lua_script, 1, lock_key, identifier)
            return bool(result)
        except Exception as e:
            self.logger.error(f"释放锁失败 {lock_key}: {e}")
            return False
    
    # ========== 批量操作 ==========
    
    def pipeline_execute(self, operations: List[tuple]) -> List:
        """执行管道操作"""
        try:
            pipeline = self.client.pipeline()
            for op in operations:
                if len(op) == 2:
                    getattr(pipeline, op[0])(*op[1])
                elif len(op) == 3:
                    getattr(pipeline, op[0])(*op[1], **op[2])
            return pipeline.execute()
        except Exception as e:
            self.logger.error(f"管道操作失败: {e}")
            return []
    
    # ========== 监控和统计 ==========
    
    def get_cluster_info(self) -> Dict:
        """获取集群信息"""
        try:
            return self.client.info()
        except Exception as e:
            self.logger.error(f"获取集群信息失败: {e}")
            return {}
    
    def get_memory_info(self) -> Dict:
        """获取内存信息"""
        try:
            return self.client.info('memory')
        except Exception as e:
            self.logger.error(f"获取内存信息失败: {e}")
            return {}
    
    def close(self):
        """关闭连接"""
        try:
            self.client.close()
            self.logger.info("Redis 连接已关闭")
        except Exception as e:
            self.logger.error(f"关闭连接失败: {e}")


# 使用示例
if __name__ == "__main__":
    # 配置日志
    logging.basicConfig(level=logging.INFO)
    
    # Redis 集群配置
    CLUSTER_NODES = [
        {"host": "127.0.0.1", "port": 7000},
        {"host": "127.0.0.1", "port": 7001},
        {"host": "127.0.0.1", "port": 7002},
    ]
    
    # 创建客户端
    redis_client = RedisClusterClient(CLUSTER_NODES)
    
    try:
        # 示例:存储用户数据
        user_data = {
            "id": 1001,
            "name": "张三",
            "email": "zhangsan@example.com",
            "created_at": "2024-01-01"
        }
        
        # 存储 JSON 数据
        redis_client.set_json("user:1001", user_data, expire=3600)
        
        # 获取 JSON 数据
        retrieved_data = redis_client.get_json("user:1001")
        print(f"用户数据: {retrieved_data}")
        
        # 使用分布式锁
        lock_key = "order:lock:1001"
        if redis_client.acquire_lock(lock_key):
            try:
                print("获得锁,执行业务操作...")
                # 这里执行需要加锁的业务逻辑
                time.sleep(1)
            finally:
                # 注意:实际使用时需要保存 identifier
                redis_client.release_lock(lock_key, "")
        else:
            print("获取锁失败,可能有其他进程在处理")
            
    finally:
        # 关闭连接
        redis_client.close()

4. 注意事项

  1. 集群节点配置:确保 startup_nodes 中至少有一个节点是可用的
  2. 键哈希槽:Redis 集群根据键的 CRC16 哈希值分配到不同的槽位
  3. 事务限制:集群模式下的事务操作必须保证所有键在同一个哈希槽
  4. 错误处理:始终添加适当的异常处理
  5. 连接池:生产环境应考虑使用连接池管理连接
  6. 监控:监控 Redis 集群的健康状态和性能指标

这个示例覆盖了 Redis 集群的大部分常用操作,你可以根据实际需求进行调整和扩展。