Redis 集群模式

· 6 min read

核心问题

单机总有上限,集群才是归宿。 主从 + 哨兵解决了高可用,却无法突破单机存储与写性能的物理瓶颈。

方案解决的问题遗留问题
单机 Redis高性能 KV 存储单点故障、CPU/内存上限
主从复制 + 哨兵高可用(故障自动切换)存储全量数据、写仍集中主节点
集群模式高可用 + 高可扩展

数据切分(分片)

核心思想

数据无限,单机有限 → 数据切分 → 分散到多个节点 → 水平扩容。

┌─────────────────────────────────────────┐
│             全量数据                      │
│  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐   │
│  │片 1  │ │片 2  │ │片 3  │ │片 4  │ …  │
│  └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘   │
│     ▼        ▼        ▼        ▼        │
│ 节点 A    节点 B    节点 C    节点 D      │
└─────────────────────────────────────────┘

切分方案演进

方案一:简单求余法(不推荐)

slot = hash(key) % N          ← N = 节点总数
  • 扩容/缩容时 N 改变 → 几乎全部数据的映射结果改变
  • 引发大规模数据迁移,成本高、风险大

方案二:哈希槽(Hash Slot)

在 key 与节点之间引入一层固定长度的数组:

hash(key) % 16384 = 槽位号(固定不变)
属性
槽总数16384(0 ~ 16383)
槽分配方式每个节点负责一段连续槽位
示例A: 05460, B: 546110922, C: 10923~16383

扩容优势:只需迁移受影响槽位中的数据,而非全部数据。


客户端路由与重定向

客户端如何找到数据?

客户端 ──→ 随机连接任一节点


       计算 hash(key) % 16384

        ┌─────┴─────┐
        ▼           ▼
   槽位归自己    槽位归他人
       │             │
   直接执行    返回 MOVED 重定向
       │             │
       ▼             ▼
     完成     客户端重连正确节点
  • cluster slots 命令可获取最新槽位分配信息
  • 主流客户端库已内置重定向逻辑,无需手动实现

数据迁移的一致性保证

迁移中的查询问题

扩容时槽位正在从旧节点 → 新节点迁移,此时查询可能落空。

解决方案:迁移状态标记

每个哈希槽维护一个「是否正在迁移」标志

旧节点收到查询:
  ├─ 数据存在 → 直接返回
  └─ 数据不存在 → 检查槽位状态
       ├─ 非迁移中 → 返回「key 不存在」
       └─ 迁移中 → 返回 ASK 重定向 → 客户端去新节点查询

最终架构

集群模式 = 数据分片 + 高可用

               ┌──────────────┐
               │   客户端     │
               └──────┬───────┘

      ┌───────────────┼───────────────┐
      ▼               ▼               ▼
┌──────────┐   ┌──────────┐   ┌──────────┐
│  主 A    │   │  主 B    │   │  主 C    │
│ 0 ~ 5460 │   │5461~10922│   │10923~... │
├──────────┤   ├──────────┤   ├──────────┤
│  从 A1   │   │  从 B1   │   │  从 C1   │
│  从 A2   │   │  从 B2   │   │  从 C2   │
└──────────┘   └──────────┘   └──────────┘

三大特性

特性说明
高可用主节点宕机,从节点自动接管
高可扩展数据分散多节点,支持水平扩容
高性能读写分散突破单机瓶颈

工作示例

3 个主节点:A(0-5460)、B(5461-10922)、C(10923-16383)

写入 set user:1001 "Katrina"

hash("user:1001") % 16384 = 1234

1234 ∈ [0, 5460] → 节点 A 直接写入 
5462 ∈ [0, 5460]? → 否 → 节点 A 返回 MOVED → 重定向到节点 B 

读取 get user:1001

hash("user:1001") % 16384 = 1234

1234 ∈ [0, 5460] → 节点 A 直接返回 "Katrina" 
5462 ∈ [0, 5460]? → 否 → 节点 A 返回 MOVED → 重定向到节点 B 读取