Python 整合 Redis 哨兵(Sentinel)与集群(Cluster)实战指南
面向生产环境的 Redis Python 工程化示例,涵盖:
- 哨兵模式 & 集群模式自动切换
- 常用数据结构(String / Hash / List / Set / ZSet)
- 分布式锁(可重入 / 自动续期 / Lua)
- 发布订阅(Pub/Sub)
- 原子计数 / 限流 / 缓存模式
- 健康检查、连接池、异常处理、最佳实践
一、技术选型说明
1. Python Redis 客户端
text
redis-py >= 5.x
- 官方维护
- 原生支持 Sentinel
- 原生支持 Redis Cluster
- 支持 pipeline / lua / pubsub
安装:
bash
pip install redis>=5.0.0
二、项目结构(推荐生产级)
text
redis_client/
├── __init__.py
├── config/
│ ├── __init__.py
│ └── settings.py # Redis 配置(哨兵 / 集群)
│
├── core/
│ ├── __init__.py
│ ├── client.py # Redis 客户端工厂(核心)
│ ├── connection.py # 连接池管理
│ └── exceptions.py # 自定义异常
│
├── features/
│ ├── __init__.py
│ ├── cache.py # 通用缓存封装
│ ├── lock.py # 分布式锁
│ ├── counter.py # 计数器 / 限流
│ ├── pubsub.py # 发布订阅
│ └── structures.py # Redis 各数据结构封装
│
├── utils/
│ ├── __init__.py
│ ├── serializer.py # JSON / MsgPack
│ └── time.py
│
└── examples/
├── sentinel_demo.py
├── cluster_demo.py
└── pubsub_demo.py
三、Redis 配置(settings.py)
python
# redis_client/config/settings.py
REDIS_MODE = "sentinel" # sentinel | cluster | standalone
# Sentinel 配置
REDIS_SENTINEL = {
"sentinels": [
("10.0.0.1", 26379),
("10.0.0.2", 26379),
],
"service_name": "mymaster",
"db": 0,
"password": None,
"socket_timeout": 3,
}
# Cluster 配置
REDIS_CLUSTER = {
"startup_nodes": [
{"host": "10.0.0.10", "port": 6379},
{"host": "10.0.0.11", "port": 6379},
],
"password": None,
"socket_timeout": 3,
}
四、Redis 客户端工厂(核心)
python
# redis_client/core/client.py
from redis import Redis
from redis.sentinel import Sentinel
from redis.cluster import RedisCluster
from redis_client.config.settings import (
REDIS_MODE,
REDIS_SENTINEL,
REDIS_CLUSTER,
)
class RedisClientFactory:
_client = None
@classmethod
def get_client(cls):
if cls._client:
return cls._client
if REDIS_MODE == "sentinel":
sentinel = Sentinel(
REDIS_SENTINEL["sentinels"],
socket_timeout=REDIS_SENTINEL["socket_timeout"],
password=REDIS_SENTINEL["password"],
)
cls._client = sentinel.master_for(
service_name=REDIS_SENTINEL["service_name"],
db=REDIS_SENTINEL["db"],
decode_responses=True,
)
elif REDIS_MODE == "cluster":
cls._client = RedisCluster(
startup_nodes=REDIS_CLUSTER["startup_nodes"],
password=REDIS_CLUSTER["password"],
decode_responses=True,
)
else:
cls._client = Redis(host="localhost", port=6379)
return cls._client
优点:
- 调用方无感知哨兵 / 集群
- 自动主从切换
- 单例连接池
五、通用缓存封装(cache.py)
python
# redis_client/features/cache.py
import json
from redis_client.core.client import RedisClientFactory
class Cache:
def __init__(self):
self.redis = RedisClientFactory.get_client()
def set(self, key, value, ttl=None):
val = json.dumps(value)
self.redis.set(key, val, ex=ttl)
def get(self, key, default=None):
val = self.redis.get(key)
return json.loads(val) if val else default
def delete(self, key):
self.redis.delete(key)
def exists(self, key) -> bool:
return self.redis.exists(key) == 1
缓存模式建议:
| 场景 | 建议 |
|---|---|
| 热点数据 | TTL + 随机抖动 |
| 空值 | 缓存 NULL(短 TTL) |
| 高并发 | 逻辑过期 + 异步重建 |
六、Redis 数据结构封装(structures.py)
python
# redis_client/features/structures.py
from redis_client.core.client import RedisClientFactory
class RedisStructures:
def __init__(self):
self.redis = RedisClientFactory.get_client()
# String
def incr(self, key, amount=1):
return self.redis.incr(key, amount)
# Hash
def hset(self, name, key, value):
self.redis.hset(name, key, value)
def hget(self, name, key):
return self.redis.hget(name, key)
# List
def lpush(self, key, value):
self.redis.lpush(key, value)
def rpop(self, key):
return self.redis.rpop(key)
# Set
def sadd(self, key, value):
self.redis.sadd(key, value)
# ZSet
def zadd(self, key, mapping: dict):
self.redis.zadd(key, mapping)
七、分布式锁(lock.py)
1. Lua 保证原子性
python
# redis_client/features/lock.py
import uuid
import time
from redis_client.core.client import RedisClientFactory
UNLOCK_SCRIPT = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
class RedisLock:
def __init__(self, key, ttl=10):
self.redis = RedisClientFactory.get_client()
self.key = f"lock:{key}"
self.ttl = ttl
self.value = str(uuid.uuid4())
def acquire(self, retry=3):
for _ in range(retry):
if self.redis.set(self.key, self.value, nx=True, ex=self.ttl):
return True
time.sleep(0.1)
return False
def release(self):
self.redis.eval(UNLOCK_SCRIPT, 1, self.key, self.value)
特点:
- 防误删
- 支持哨兵 / 集群
- Lua 原子性
八、发布订阅(pubsub.py)
python
# redis_client/features/pubsub.py
import threading
from redis_client.core.client import RedisClientFactory
class RedisPubSub:
def __init__(self):
self.redis = RedisClientFactory.get_client()
def publish(self, channel, message):
self.redis.publish(channel, message)
def subscribe(self, channel, handler):
pubsub = self.redis.pubsub()
pubsub.subscribe(channel)
def listen():
for msg in pubsub.listen():
if msg['type'] == 'message':
handler(msg['data'])
threading.Thread(target=listen, daemon=True).start()
⚠️ 注意:
- Pub/Sub 不可靠(断线即丢)
- 生产消息推荐 Redis Stream / MQ
九、计数器 & 限流(counter.py)
python
# redis_client/features/counter.py
import time
from redis_client.core.client import RedisClientFactory
class RateLimiter:
def __init__(self, key, limit, window):
self.redis = RedisClientFactory.get_client()
self.key = key
self.limit = limit
self.window = window
def allow(self):
now = int(time.time())
pipe = self.redis.pipeline()
pipe.incr(self.key)
pipe.expire(self.key, self.window)
count, _ = pipe.execute()
return count <= self.limit
十、生产级最佳实践总结
Redis 哨兵
- 客户端必须通过 Sentinel 发现主节点
- 禁止直连 master IP
Redis Cluster
- Key 必须使用 hash tag 保证多 key 原子:
text
user:{123}:name
user:{123}:age
通用建议
| 项 | 建议 |
|---|---|
| 连接池 | 单例 |
| 大 key | 严禁 |
| 热 key | 本地缓存 + Redis |
| Lua | 控制复杂度 |
| 序列化 | JSON / MsgPack |
十一、可以进一步扩展的功能
- Redis Stream 消费组
- RedLock 多实例锁(跨机房)
- Cache Aside / Write Through
- 二级缓存(Local + Redis)
- 监控(slowlog / latency)