四种部署模式演进
模式一:单机模式
模式二:主从复制(Replication)
# Slave 配置(redis.conf)
replicaof 192.168.1.10 6379 # 指定 Master
replica-read-only yes # Slave 只读
replica-lazy-flush no # 全量同步时是否异步清空旧数据
模式三:哨兵模式(Sentinel)
# 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 是官方原生的分布式解决方案,支持数据自动分片、节点间通信、自动故障转移,是大规模部署的首选。
哈希槽(Hash Slot)分片算法
Redis Cluster 使用固定的 16384 个哈希槽(Hash Slot)对数据分片:
为什么是 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() 方法,自动处理多节点扫描。
gossip 协议深度解析
Redis Cluster 没有集中式的协调节点(如 ZooKeeper),而是通过 gossip 协议在节点间自主传播集群状态,实现去中心化管理:
Cluster 数据迁移(槽重新分配)
向集群添加新节点时,需要将部分槽从旧节点迁移到新节点,期间不影响服务:
# 添加新节点到集群
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
# 重新分片:将 1000 个槽迁移到新节点
redis-cli --cluster reshard 127.0.0.1:7001 \
--cluster-from all \
--cluster-to <new-node-id> \
--cluster-slots 1000 \
--cluster-yes
# 查看迁移进度
redis-cli --cluster check 127.0.0.1:7001
# 删除节点(需先将其槽全部迁走)
redis-cli --cluster del-node 127.0.0.1:7001 <node-id>
迁移期间的 MOVED 和 ASK 重定向:迁移期间,客户端请求某个槽的 key 时,可能收到 MOVED(槽已完全迁走)或 ASK(槽正在迁移中)响应。redis-py 等客户端会自动处理重定向,但如果使用裸连接或自研客户端,必须实现这两种重定向逻辑,否则会出现"访问错误节点"的错误。
Cluster 模式下的 ACL
Redis 6.0+ 的 ACL 在 Cluster 中为每个节点独立配置,但可以使用 ACL 文件在所有节点保持一致:
# 在所有节点的 redis.conf 中引用同一份 ACL 文件
aclfile /etc/redis/users.acl
# /etc/redis/users.acl
# 格式:user <username> <flags> <password> <commands> <keys> <channels>
# 应用服务用户:只能读写 app: 前缀的 key,允许所有频道
user app-service on >app-secret-2024 ~app:* &* +@read +@write +@string +@hash -FLUSHDB -FLUSHALL
# 只读监控用户:查看信息但不能修改数据
user monitor on >monitor-pass ~* &* +INFO +DBSIZE +CLUSTER +LATENCY -@write
# 热更新 ACL(无需重启,所有节点执行)
redis-cli -a <admin-pass> ACL LOAD
# 验证权限
redis-cli -u "redis://app-service:app-secret-2024@localhost:7001" ACL WHOAMI
Cluster 监控与运维命令
# 集群整体状态(cluster_state: ok 表示正常)
redis-cli -p 7001 CLUSTER INFO
# 各节点状态(查看主从关系、槽分配、节点 ID)
redis-cli -p 7001 CLUSTER NODES
# 检查集群健康状态(包括槽覆盖情况)
redis-cli --cluster check 127.0.0.1:7001
# 修复集群(处理槽未完全覆盖等问题)
redis-cli --cluster fix 127.0.0.1:7001
# 各节点负载均衡情况
redis-cli --cluster rebalance 127.0.0.1:7001 --cluster-use-empty-masters
# 查看某个 key 属于哪个槽和节点
redis-cli -p 7001 CLUSTER KEYSLOT user:1001
redis-cli -p 7001 CLUSTER GETKEYSINSOT <slot> <count>
Redis 部署模式从简到复杂依次是:单机 → 主从复制(读写分离)→ Sentinel(自动故障转移)→ Cluster(数据分片 + 高可用)。
Cluster 核心机制:16384 个哈希槽平均分配给 Master 节点,CRC16(key)%16384 决定 key 落在哪个槽。哈希标签 {tag} 可强制多 key 同槽,解决 MSET/事务跨槽问题。
高可用:gossip 协议实现去中心化节点通信;超半数 Master 确认故障才触发转移,防止误判;min-replicas-to-write 防止脑裂导致数据丢失。
运维注意:Cluster 只能使用 DB 0;SCAN 需对每个 Master 分别执行;Lua 脚本和事务只能操作同槽 key;ACL 需同步到所有节点。
何时使用 Cluster:单节点内存不足(>100GB)、或需要写操作水平扩展时。中小规模(单机 32~64GB)优先使用 Sentinel,运维更简单。