一句话总结
Redis 集群方案演进:主从复制(读写分离、数据冗余)→ Sentinel 哨兵(自动故障转移)→ Cluster 集群(数据分片、水平扩展)。Cluster 使用 16384 个哈希槽(CRC16(key) % 16384)分配数据,节点间通过 Gossip 协议通信。客户端访问错误节点时返回 MOVED(槽已迁移)或 ASK(槽迁移中)重定向。
初级理解
三种集群方案对比
| 方案 | 数据分片 | 高可用 | 扩展性 | 复杂度 |
|---|---|---|---|---|
| 主从复制 | ❌ 不分片 | ❌ 手动切换 | ❌ 垂直扩展 | 低 |
| Sentinel | ❌ 不分片 | ✅ 自动故障转移 | ❌ 垂直扩展 | 中 |
| Cluster | ✅ 哈希槽分片 | ✅ 内置故障转移 | ✅ 水平扩展 | 高 |
# 主从复制配置
# 从库配置
replicaof 192.168.1.100 6379 # 指定主库
# Sentinel 配置(sentinel.conf)
sentinel monitor mymaster 192.168.1.100 6379 2
# 2 个 Sentinel 认为主库下线才触发故障转移
# Cluster 配置
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
一句话总结:主从复制做读写分离,Sentinel 做自动故障转移,Cluster 做数据分片和水平扩展。
中级深入
主从复制 — 全量同步与增量同步
# 全量同步(首次连接或复制偏移量差距太大)
# 1. 从库发送 PSYNC ? -1(请求全量同步)
# 2. 主库执行 BGSAVE 生成 RDB,同时用 replication buffer 记录新命令
# 3. 主库发送 RDB 给从库
# 4. 从库加载 RDB
# 5. 主库发送 buffer 中的增量命令
# 增量同步(短暂断连后重连)
# 1. 从库发送 PSYNC replid offset
# 2. 主库检查 replid 是否匹配
# 3. 如果 offset 在 repl_backlog_buffer 范围内 → 增量同步
# 4. 否则 → 全量同步
# repl_backlog_buffer 配置
repl-backlog-size 64mb # 环形缓冲区大小
# 越大越能容忍短暂断连
Sentinel — 故障转移流程
# 故障转移流程
# 1. 主观下线(SDOWN):单个 Sentinel 认为主库不可达
# 2. 客观下线(ODOWN):quorum 个 Sentinel 都认为主库不可达
# 3. Leader 选举:Sentinel 之间 Raft 选举一个 Leader
# 4. Leader 选新主库:从从库中选一个最优的
# 选择标准:优先级 > 复制偏移量 > runid
# 5. 通知其他从库复制新主库
# 6. 通知客户端新主库地址
# Sentinel 配置
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 30000 # 30秒无响应判定下线
sentinel failover-timeout mymaster 180000 # 故障转移超时
sentinel parallel-syncs mymaster 1 # 同时同步的从库数
Cluster — 哈希槽与重定向
# 哈希槽分配
# 总共 16384 个槽
# CRC16(key) % 16384 → 确定槽 → 确定节点
# 分配示例(3 主 3 从)
# 节点 A: 槽 0-5460
# 节点 B: 槽 5461-10922
# 节点 C: 槽 10923-16383
# MOVED 重定向(槽已迁移)
GET user:1001
# → MOVED 3999 192.168.1.101:6379
# 客户端收到 MOVED 后更新槽映射,重新请求
# ASK 重定向(槽迁移中)
GET user:1001
# → ASK 3999 192.168.1.101:6379
# 客户端发送 ASKING 命令后重新请求
# ASK 是临时重定向,不更新槽映射
中级要点:主从全量同步用 RDB + buffer;Sentinel 通过 Raft 选 Leader 执行故障转移;Cluster 用哈希槽分片。
高级拓展
Gossip 协议 — Cluster 节点通信
Cluster 节点间通过 Gossip 协议交换信息,包括节点状态、槽分配等。每个节点随机选择几个节点发送 PING,收到 PONG 回复。
# Gossip 消息类型
# MEET: 邀请新节点加入集群
# PING: 心跳检测 + 交换信息
# PONG: 回复 PING/MEET
# FAIL: 广播节点下线
# PUBLISH: 发布订阅消息
# 为什么是 16384 个槽?
# 1. 心跳包中包含槽信息(bitmap),16384 位 = 2KB
# 2. 65536 个槽 → 8KB 心跳包太大
# 3. 节点数通常不超过 1000,16384 足够均匀分配
Cluster 限制
# Cluster 不支持的操作
# 1. 多 key 操作(除非 key 在同一槽)
# 解决:用 {} 指定 hash tag
# MSET {user:1}.name "张三" {user:1}.age 18
# # {} 内的部分用于计算哈希槽
# 2. 事务(MULTI/EXEC)跨节点不支持
# 3. Lua 脚本中涉及的 key 必须在同一节点
# 4. 批量操作需要客户端实现(如 pipeline 按节点分组)
实战场景
场景:Cluster 扩容
# 1. 启动新节点
redis-server --port 6380 --cluster-enabled yes
# 2. 加入集群
redis-cli --cluster add-node 192.168.1.104:6380 192.168.1.101:6379
# 3. 重新分配槽
redis-cli --cluster reshard 192.168.1.101:6379
# 输入要迁移的槽数、目标节点 ID、源节点 ID
# 4. 添加从节点
redis-cli --cluster add-node 192.168.1.105:6380 192.168.1.101:6379 \
--cluster-slave --cluster-master-id <master-id>
# 5. 验证
redis-cli --cluster check 192.168.1.101:6379
面试模拟
面试官:Redis Cluster 的数据分片原理?
你:使用 16384 个哈希槽,CRC16(key) % 16384 确定槽,每个节点负责一部分槽。客户端访问错误节点时返回 MOVED 重定向(槽已迁移)或 ASK 重定向(槽迁移中)。节点间通过 Gossip 协议交换槽映射信息。
面试官:Sentinel 怎么判断主库下线?
你:分两步:1)主观下线(SDOWN):单个 Sentinel 在 down-after-milliseconds 时间内收不到主库响应,认为主库主观下线;2)客观下线(ODOWN):quorum 个 Sentinel 都认为主库主观下线,则判定为客观下线。客观下线后 Sentinel 之间通过 Raft 选举 Leader 执行故障转移。