小陈的知识图谱
RedisL3 深入核心重点

集群模式

主从、Sentinel、Cluster、数据分片、高可用

集群模式

概述

单机 Redis 存在三个瓶颈:容量有限(单机内存上限)、性能有限(单线程处理能力)、可用性有限(单点故障)。为了解决这些问题,Redis 提供了三种集群架构:

架构解决的核心问题复杂度推荐场景
主从架构读写分离、数据冗余读写分离场景
Sentinel(哨兵)自动故障转移、高可用需要自动 HA 的场景
Cluster(集群)数据分片、水平扩展海量数据、高吞吐场景

实际生产环境中,这三种架构往往会组合使用。

---

主从架构(Replication)

主从复制是 Redis 高可用的基础。一个主库(Master)可以有多个从库(Slave),从库实时同步主库的数据。

架构图

主从复制架构

                    ┌──────────────┐
                    │   客户端      │
                    │  (读写分离)   │
                    └──────┬───────┘
                           │ 写入
                           ▼
                    ┌──────────────┐
            ┌──────│   Master     │──────┐
            │      │  (主库)      │      │
            │      └──────────────┘      │
            │ 复制                   复制 │
            ▼                            ▼
     ┌──────────────┐           ┌──────────────┐
     │   Slave 1    │           │   Slave 2    │
     │  (从库-只读)  │           │  (从库-只读)  │
     └──────────────┘           └──────────────┘
            │                          │
            │ 客户端读取                │
            ▼                          ▼
     ┌──────────────┐           ┌──────────────┐
     │   客户端      │           │   客户端      │
     │  (读请求)    │           │  (读请求)    │
     └──────────────┘           └──────────────┘

配置方式

# 方式一:在 slave 的 redis.conf 中配置
replicaof 192.168.1.100 6379

方式二:运行时命令(临时生效)

127.0.0.1:6379> REPLICAOF 192.168.1.100 6379

方式三:在 slave 启动时通过参数传入

redis-server --port 6380 --replicaof 192.168.1.100 6379

查看复制状态

127.0.0.1:6380> INFO replication

输出关键信息:

role:slave

master_host:192.168.1.100

master_port:6379

master_link_status:up

slave_repl_offset: 12345

全量复制流程

当新的 Slave 加入或 Slave 与 Master 断开后重连时,先进行全量复制:

全量复制流程图

  Slave                          Master
    │                               │
    │──── psync ? -1 ───────────────►│
    │                               │── 当前 replication ID: abc
    │                               │── 当前 offset: 12345
    │◄── FULLRESYNC abc 12345 ──────│
    │                               │
    │                               ├── BGSAVE 生成 RDB 快照
    │                               │── 同时将新命令写入 buffer
    │                               │
    │◄────── RDB 文件 ──────────────│
    │                               │
    ├── 清空当前内存                  │
    ├── 加载 RDB 文件                │
    │                               │
    │◄── 继续发送 buffer 中的命令 ────│
    │                               │
    ├── 执行 buffer 命令             │
    ├── 开始增量复制                 │
    │──── PSYNC abc 12345 ─────────►│
    │◄── 实时推送新命令 ─────────────│
    │                               │

增量复制流程

当 Slave 断开后短时间内重连,且 Master 的复制积压缓冲区(repl-backlog-size)中还有 Slave 断连期间的数据时,进行增量复制:

增量复制流程图

  ① Slave 重连后发送 psync 命令:PSYNC abc 10000
  ② Master 检查自己的 backlog:
     - repl_backlog_histoff = 5000
     - repl_backlog 包含 offset 5000 ~ 15000 的数据
  ③ Slave 需要的 offset 10000 在 backlog 范围内
  ④ Master 回复:CONTINUE
  ⑤ Master 从 offset 10000 开始发送 backlog 中的部分数据
  ⑥ Slave 接收并执行

  关键配置(redis.conf):
  repl-backlog-size 1mb    # 复制积压缓冲区大小(建议 10-100mb)
  # 缓冲区越大,容忍 Slave 断线的时间越长

主从配置示例(本地模拟)

# 启动三个 Redis 实例

主库:6379

redis-server --port 6379

从库 1:6380

redis-server --port 6380 --replicaof 127.0.0.1 6379

从库 2:6381

redis-server --port 6381 --replicaof 127.0.0.1 6379

验证

redis-cli -p 6379 SET test "hello" # 主库写入 redis-cli -p 6380 GET test # 从库可以读到 redis-cli -p 6381 GET test # 从库可以读到 redis-cli -p 6380 SET test2 "world" # 错误!(error) READONLY

---

Redis Sentinel(哨兵模式)

哨兵模式在主从架构的基础上增加了 自动故障转移 能力。哨兵本身是一个独立的 Redis 进程,用于监控主库和从库的状态。

架构图

Sentinel 架构

              ┌─────────────────────────┐
              │   Sentinel 集群          │
              │  ┌─────┐ ┌─────┐ ┌─────┐ │
              │  │ S1  │ │ S2  │ │ S3  │ │
              │  └──┬──┘ └──┬──┘ └──┬──┘ │
              └─────┼────────┼────────┼───┘
                    │ 监控    │ 监控    │
                    ▼         ▼        ▼
              ┌──────────┐        ┌──────────┐
              │  Master   │───────│  Slave 1  │
              │  (主库)   │ 复制   │  (从库)   │
              └─────┬────┘        └──────────┘
                    │                    │
                    │ 复制                │
                    ▼                    ▼
              ┌──────────┐        ┌──────────┐
              │  Slave 2  │        │   客户端  │
              │  (从库)   │        │  (连接哨兵 │
              └──────────┘        │   获取地址)│
                                  └──────────┘

配置部署

# sentinel.conf 配置
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2  # 2:至少 2 个哨兵同意才判定主库下线
sentinel down-after-milliseconds mymaster 5000   # 5 秒无响应视为下线
sentinel failover-timeout mymaster 60000          # 故障转移超时 60 秒
sentinel parallel-syncs mymaster 1                # 新主库同时同步的从库数量

启动哨兵

redis-sentinel sentinel.conf

redis-server sentinel.conf --sentinel

客户端通过哨兵获取主库信息

redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

故障转移流程

故障转移详细流程

  阶段一:主观下线
  ┌─────────────────────────────────────────────┐
  │  Sentinel 每 1 秒向 Master 发 PING          │
  │  → 超过 down-after-milliseconds 无响应       │
  │  → Sentinel 标记 master 为 sdown(主观下线)  │
  └─────────────────────────────────────────────┘

  阶段二:客观下线
  ┌─────────────────────────────────────────────┐
  │  Sentinel 通过 SENTINEL is-master-down-by-addr │
  │  向其他 Sentinel 询问 Master 状态             │
  │  → 超过 quorum(配置中的 2)个节点确认下线     │
  │  → 标记为 odown(客观下线)                   │
  └─────────────────────────────────────────────┘

  阶段三:Leader 选举
  ┌─────────────────────────────────────────────┐
  │  使用 Raft 算法在 Sentinel 中选举 Leader     │
  │  → 每个周期只有一个 Sentinel 成为 Leader      │
  │  → Leader 负责执行故障转移                    │
  └─────────────────────────────────────────────┘

  阶段四:从库选择
  ┌─────────────────────────────────────────────┐
  │  Leader Sentinel 从从库中选新主库:           │
  │  ① 优先选 replica-priority 最小的            │
  │  ② 优先选复制偏移量最大的(数据最新)         │
  │  ③ 优先选 runid 最小的                       │
  └─────────────────────────────────────────────┘

  阶段五:角色切换
  ┌─────────────────────────────────────────────┐
  │  ① 执行 SLAVEOF NO ONE 提升为 Master         │
  │  ② 修改其他 Slave 的复制目标为新 Master       │
  │  ③ 原 Master 恢复后设为新 Master 的 Slave     │
  └─────────────────────────────────────────────┘

// Java 通过 JedisSentinelPool 连接哨兵管理的 Redis
import redis.clients.jedis.JedisSentinelPool;

public class SentinelExample {
    public static void main(String[] args) {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("192.168.1.100:26379");
        sentinels.add("192.168.1.101:26379");
        sentinels.add("192.168.1.102:26379");

        // 创建哨兵连接池(自动获取当前主库地址)
        try (JedisSentinelPool pool = new JedisSentinelPool(
                "mymaster", sentinels)) {

            // 获取连接(pool 自动处理故障转移)
            try (Jedis jedis = pool.getResource()) {
                jedis.set("key", "value");
                System.out.println(jedis.get("key"));
            }
        }

        // 哨兵故障转移期间:
        // ① pool 会检测到连接失效
        // ② 向哨兵查询新主库地址
        // ③ 重建连接池
        // 整个过程对业务代码透明!
    }
}

---

Redis Cluster

Redis Cluster 是真正的分布式解决方案,数据自动分片到多个节点,每个节点负责一部分数据。它解决了单机容量和吞吐量的瓶颈。

架构图

Redis Cluster 架构(3 主 3 从)

             ┌────────────────────────────┐
             │       客户端应用             │
             │   (连接任意节点,自动重定向)  │
             └──────┬──────┬──────┬───────┘
                    │      │      │
                    ▼      ▼      ▼
             ┌──────────┐ ┌──────────┐ ┌──────────┐
             │ Node A   │ │ Node B   │ │ Node C   │
             │ Master   │ │ Master   │ │ Master   │
             │ Slots:   │ │ Slots:   │ │ Slots:   │
             │ 0-5460   │ │ 5461-    │ │ 10923-   │
             │          │ │ 10922    │ │ 16383    │
             └────┬─────┘ └────┬─────┘ └────┬─────┘
                  │            │            │
                  │ 复制       │ 复制       │ 复制
                  ▼            ▼            ▼
             ┌──────────┐ ┌──────────┐ ┌──────────┐
             │ Node D   │ │ Node E   │ │ Node F   │
             │ Slave    │ │ Slave    │ │ Slave    │
             └──────────┘ └──────────┘ └──────────┘

  节点间通过 Gossip 协议交换元数据
  所有节点互联(PING/PONG 消息)

哈希槽分片机制

Redis Cluster 分片核心逻辑

  key: "user:1001"

  hash = CRC16("user:1001")     # CRC16 计算 16 位哈希值
                                # CRC16 返回 0-65535 的值

  slot = hash % 16384           # slot = 12345

  # 根据 slot 找到对应节点:
  # Node A: slots 0-5460
  # Node B: slots 5461-10922
  # Node C: slots 10923-16383

  # user:1001 → slot 12345 → Node C

Hash Tag 用法:当需要确保多个 key 在同一个 slot 时,使用 {} 标记:

# 普通 key:可能分配到不同 slot
SET user:1001 "data"    # slot: 12345
SET user:1002 "data"    # slot: 67890  ← 可能不同节点

这两个 key 可能不在同一节点,无法直接做交集等操作

使用 Hash Tag:{} 内的内容参与哈希计算

SET {user}:1001 "data" # slot: 12345 SET {user}:1002 "data" # slot: 12345 ← 相同节点!

两个 key 现在可以在同一节点操作(如 MSET)

Hash Tag 的计算:只对 {} 内的 {user} 做 CRC16

部署与操作

# 1. 启动 6 个节点(3 主 3 从)
redis-server --port 7000 --cluster-enabled yes --cluster-config-file nodes-7000.conf
redis-server --port 7001 --cluster-enabled yes --cluster-config-file nodes-7001.conf
redis-server --port 7002 --cluster-enabled yes --cluster-config-file nodes-7002.conf
redis-server --port 7003 --cluster-enabled yes --cluster-config-file nodes-7003.conf
redis-server --port 7004 --cluster-enabled yes --cluster-config-file nodes-7004.conf
redis-server --port 7005 --cluster-enabled yes --cluster-config-file nodes-7005.conf

2. 创建集群(分配 slots)

redis-cli --cluster create \ 127.0.0.1:7000 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 \ --cluster-replicas 1 # 每个主节点配 1 个从节点

3. 访问集群

redis-cli -c -p 7000 -h 127.0.0.1 # -c 表示集群模式 127.0.0.1:7000> SET foo bar -> Redirected to slot [12182] located at 127.0.0.1:7002 # 自动重定向 OK

4. 查看集群信息

redis-cli -p 7000 CLUSTER INFO redis-cli -p 7000 CLUSTER NODES

集群命令与节点管理

# 查看 key 所在的 slot
CLUSTER KEYSLOT user:1001

查看 slots 分布

CLUSTER SLOTS

添加新节点

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000

重新分配 slots(迁移数据)

redis-cli --cluster reshard 127.0.0.1:7000

删除节点

redis-cli --cluster del-node 127.0.0.1:7006 <node-id>

// Java 使用 JedisCluster 连接集群
import redis.clients.jedis.JedisCluster;

public class ClusterExample {
    public static void main(String[] args) {
        Set<HostAndPort> clusterNodes = new HashSet<>();
        clusterNodes.add(new HostAndPort("192.168.1.100", 7000));
        clusterNodes.add(new HostAndPort("192.168.1.101", 7001));
        clusterNodes.add(new HostAndPort("192.168.1.102", 7002));

        // JedisCluster 自动处理:
        // ① 初始化时从任意节点获取集群拓扑
        // ② 计算 key 的 slot,直连对应节点
        // ③ 遇到 MOVED 重定向时更新本地缓存
        try (JedisCluster cluster = new JedisCluster(clusterNodes)) {
            // 写入 → 自动路由到正确节点
            cluster.set("key1", "value1");
            cluster.set("key2", "value2");

            // 批量操作限制:只能在同一个 slot 内
            // 以下操作会报错(key1 和 key2 可能不在同一节点)
            // cluster.mset("key1", "v1", "key2", "v2");

            // 使用 hash tag 修复:
            cluster.set("{group}:key1", "v1");
            cluster.set("{group}:key2", "v2");
            // 现在这两个 key 肯定在同一节点
        }
    }
}

---

Gossip 协议

Redis Cluster 的节点之间通过 Gossip 协议 交换信息,这是一种去中心化的通信方式。

消息类型

消息类型说明
MEET请求加入集群
PING定期探测节点是否存活(频率:每秒随机选 5 个节点)
PONG回应 PING,包含自身状态信息
FAIL广播某个节点已宕机

通信流程

Gossip 通信

  每秒钟,每个节点:

  ① 从已知节点列表中随机选 1 个未通信时间最长的节点
  ② 发送 PING 消息(包含已知节点信息)
  ③ 对方回复 PONG(同步状态)

  ④ 如果收到 PONG 的节点不在本节点列表中
     → 记录新节点(MEET 消息感知新节点)

  ⑤ 如果某个节点超过 node-timeout 未响应
     → 标记为疑似下线(PFAIL)
     → 通过 Gossip 传播给其他节点
     → 超过半数节点确认 → 标记为 FAIL → 广播

集群的限制

Redis Cluster 有以下限制,设计时需要注意:

1. 不支持多键操作(key 在不同 slot 时)

# MSET、MGET、SINTER 等操作要求所有 key 在同一个 slot
   # 解决方案:使用 Hash Tag
   MSET {user}:1001 "a" {user}:1002 "b"   # 可以
   MSET user:1001 "a" user:1002 "b"       # 可能不行

2. 不支持多数据库(只能使用 db 0)

# 在集群模式下,SELECT 命令不可用
   SELECT 1
   (error) ERR SELECT is not allowed in cluster mode

3. Pipeline 和事务的限制

- Pipeline 可以跨节点,但每个节点独立执行

- MULTI/EXEC 事务内的所有 key 必须在同一节点

4. 数据分布不均:某些 key 访问太热导致节点负载不均衡

5. 集群可用性:超过半数主节点不可用,集群整体不可用

---

面试技巧与最佳实践

常见面试题

1. 主从复制的核心原理?

Slave 发送 psync 命令,Master 执行 BGSAVE 生成 RDB 发送给 Slave,然后持续发送增量命令。

2. 全量复制和增量复制的区别?

- 全量复制:发送完整 RDB 文件,用于新节点加入或断连太久

- 增量复制:从 backlog 缓冲区发送部分数据,用于短时间断连

3. Sentinel 如何选主?

- 优先 replica-priority 最小

- 其次复制偏移量最大(数据最新)

- 再次 runid 最小

4. Cluster 的哈希槽是怎么分配的?

16384 个槽,CRC16(key) % 16384 计算 slot,每个节点负责一段连续的 slot 范围。

5. Cluster 为什么是 16384 个槽而不是更多?

- 16384 个槽的元数据约 2KB(bitmap 表示),Gossip 消息小

- CRC16 输出 16 位,取模 16384 正好映射完整输出空间

- 太多槽增加元数据传播开销

6. 集群模式下如何执行批量操作?

确保 key 使用相同 Hash Tag,或者客户端自己做分组按节点发送。

实战注意事项

  • 主从一定要跨机房部署:避免单机房故障导致全部不可用
  • repl-backlog-size 要足够大:用 (主库写入速度) × (期望容忍断连秒数) 计算
  • 集群最少 6 个节点(3 主 3 从),保证高可用
  • Cluster 不支持事务跨 slot:设计 key 时要规划好 Hash Tag
  • 集群扩容时注意数据迁移对性能的影响:reshard 期间节点压力增大
  • 客户端缓存集群拓扑:遇到 MOVED/ASK 重定向时更新本地节点映射
  • 不要在 slave 上执行消耗性能的操作(如 KEYS、SORT、大范围查询)

核心要点

  • 主从复制原理与全量/增量同步
  • Sentinel 哨兵模式与故障转移
  • Cluster 哈希槽分片机制
  • Gossip 协议通信
  • 集群限制与 hash tag 使用