四种部署模式演进
模式一:单机模式
Client ──▶ Redis(单节点)
优点:简单 缺点:单点故障,容量受单机内存限制
模式二:主从复制(Replication)
Client ──写──▶ Master ──异步复制──▶ Slave1
──读──▶ Slave1 ──▶ Slave2
优点:读写分离,Slave 可作备份
缺点:Master 故障需手动切换,有数据丢失风险(异步复制)
# Slave 配置(redis.conf)
replicaof 192.168.1.10 6379 # 指定 Master
replica-read-only yes # Slave 只读
replica-lazy-flush no # 全量同步时是否异步清空旧数据
模式三:哨兵模式(Sentinel)
Sentinel 高可用架构
Sentinel1 Sentinel2 Sentinel3 ← 奇数个 Sentinel
↑ ↑ ↑
└───────────┴───────────┘ 监控
↓
Master (6379)
↙ ↘
Slave1 (6380) Slave2 (6381)
Master 故障时:Sentinel 投票选出新 Master,自动完成故障转移
# sentinel.conf
sentinel monitor mymaster 192.168.1.10 6379 2 # 2个Sentinel认为故障才切换
sentinel down-after-milliseconds mymaster 5000 # 5秒无响应认为下线
sentinel failover-timeout mymaster 60000 # 故障转移超时60秒
模式四:Cluster 集群
Redis Cluster 是官方原生的分布式解决方案,支持数据自动分片、节点间通信、自动故障转移,是大规模部署的首选。
Redis Cluster 最小配置(3主3从)
Master1 Master2 Master3
槽 0-5460 槽 5461-10922 槽 10923-16383
↕ ↕ ↕
Slave1 Slave2 Slave3
节点间通过 gossip 协议通信(端口 = 服务端口 + 10000)
哈希槽(Hash Slot)分片算法
Redis Cluster 使用固定的 16384 个哈希槽(Hash Slot)对数据分片:
键到槽的映射
slot = CRC16(key) % 16384
SET user:1001 "Alice"
↓
CRC16("user:1001") = 12891
↓
12891 % 16384 = 12891
↓
落在 Master3 负责的槽范围(10923-16383)
↓
请求路由到 Master3
为什么是 16384 个槽?
16384(2^14)是一个平衡点:槽数量越多,节点间传播槽信息的心跳包越大;越少,节点数量扩展受限。作者认为 16384 个槽,1000 个节点的集群中心跳包约 2KB,可以接受。
哈希标签(Hash Tag)
通过 {} 标签强制多个 key 落在同一槽,解决 MSET、pipeline、事务跨槽的限制:
# 不带标签:三个 key 可能在不同槽
SET user:1001:name "Alice"
SET user:1001:age 25
SET user:1001:city "Shanghai"
# 带哈希标签:花括号内的内容决定槽
# CRC16("{user:1001}") % 16384,三个 key 在同一槽
SET {user:1001}:name "Alice"
SET {user:1001}:age 25
SET {user:1001}:city "Shanghai"
# 现在可以在 pipeline 中一起操作
搭建 Cluster(Docker Compose)
# docker-compose.yml
version: '3.8'
services:
redis-1: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7001, ports: ["7001:7001"] }
redis-2: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7002, ports: ["7002:7002"] }
redis-3: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7003, ports: ["7003:7003"] }
redis-4: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7004, ports: ["7004:7004"] }
redis-5: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7005, ports: ["7005:7005"] }
redis-6: { image: redis:7-alpine, command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --port 7006, ports: ["7006:7006"] }
# 启动并初始化集群(--cluster-replicas 1 表示每个 Master 1个 Slave)
docker-compose up -d
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1
# 查看集群状态
redis-cli -p 7001 cluster info
redis-cli -p 7001 cluster nodes
Python 连接 Cluster
from redis.cluster import RedisCluster, ClusterNode
# 连接集群(只需指定部分节点,客户端自动发现其余节点)
startup_nodes = [
ClusterNode("127.0.0.1", 7001),
ClusterNode("127.0.0.1", 7002),
]
rc = RedisCluster(
startup_nodes=startup_nodes,
decode_responses=True,
skip_full_coverage_check=True
)
# 单 key 操作:自动路由到正确节点
rc.set("user:1001", "Alice")
print(rc.get("user:1001"))
# 批量操作:使用哈希标签确保同一槽
with rc.pipeline() as pipe:
pipe.set("{user:1001}:name", "Alice")
pipe.set("{user:1001}:age", "25")
pipe.execute()
gossip 协议与脑裂问题
gossip 通信
Cluster 节点通过 gossip 协议相互传播节点状态、槽分配信息。每个节点定期向其他节点发送 PING,维护完整的集群视图,无需中心协调节点。
脑裂问题
网络分区时,部分节点互相隔离。如果隔离的 Slave 被选为 Master,而原 Master 也继续服务,就会出现"双 Master"(脑裂),导致数据不一致。
# 防止脑裂配置:当副本数不足时,Master 停止写入
min-replicas-to-write 1 # 至少有1个副本在同步才允许写
min-replicas-max-lag 10 # 副本延迟不超过10秒
Cluster 的限制
| 限制 | 原因 | 解决方案 |
|---|---|---|
| MSET/MGET 跨槽报错 | 多个 key 分布在不同节点 | 使用哈希标签 {tag} |
| 事务(MULTI/EXEC)跨槽报错 | 同上 | 哈希标签 / Lua 脚本(同槽内有效) |
| 只有 DB 0 可用 | Cluster 模式禁用 SELECT | 改用 key 前缀区分业务 |
| Pub/Sub 广播效率低 | 消息需广播到所有节点 | 使用 SSUBSCRIBE(分片 Pub/Sub) |
| Lua 脚本只能操作同槽 key | 同上 | 设计时保证 Lua 访问的 key 在同一槽 |
Cluster 下的 SCAN 陷阱:在 Cluster 模式下,SCAN 命令只扫描当前连接节点上的 key,无法跨节点扫描全部 key。如需扫描所有 key,需要对每个 Master 节点分别执行 SCAN,并在客户端合并结果。redis-py 的 RedisCluster 提供了 scan_iter() 方法,自动处理多节点扫描。