Chapter 08

Redis Cluster 集群

从单机到 Cluster——四种部署模式的演进、16384 个槽的分片设计与高可用原理

四种部署模式演进

模式一:单机模式

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() 方法,自动处理多节点扫描。