Files

7.8 KiB
Raw Permalink Blame History

tags, create time, update time
tags create time update time
redis
cluster-sentinel
sharding
gossip
failover
2026-08-08 18:43 2026-08-08 18:43

集群与哨兵机制

概述

Redis 单实例架构天然面临内存上限和单点故障问题。Redis Cluster(原生分片集群)解决了水平扩展,Sentinel(哨兵)解决了高可用自动故障转移。理解它们的架构、通信协议和选举机制,是构建生产级 Redis 基础设施的前提。

核心原理

Sentinel — 哨兵架构

哨兵不是对数据进行分片的,而是在主从复制的基础上增加自动化故障检测和恢复能力。

哨兵节点组成:

graph TB
    subgraph RedisCluster["Redis 主从集群"]
        M["Master<br/>192.168.1.10:6379"]
        S1["Slave1<br/>192.168.1.11:6379"]
        S2["Slave2<br/>192.168.1.12:6379"]
    end

    subgraph Sentinels["哨兵集群"]
        SEN1["Sentinel-1<br/>192.168.1.10:26379"]
        SEN2["Sentinel-2<br/>192.168.1.11:26379"]
        SEN3["Sentinel-3<br/>192.168.1.12:26379"]
    end

    subgraph Clients["客户端"]
        APP1["应用服务A"]
        APP2["应用服务B"]
    end

    M -->|replication| S1
    M -->|replication| S2
    SEN1 -->|监控| M
    SEN2 -->|监控| M
    SEN3 -->|监控| M
    SEN1 <-->|Gossip通信| SEN2
    SEN2 <-->|Gossip通信| SEN3
    SEN3 <-->|Gossip通信| SEN1
    APP1 -->|连接| M
    APP2 -->|连接| M

四大职责:

  1. 监控(Monitoring):定期检查 Master、Slave 和其他 Sentinel 是否可达(通过 PING)。
  2. 提醒(Notification):当某个节点发现异常时,通知其他 Sentinel。
  3. 自动故障转移(Failover):当 master 被标记为客观下线(ODOWN)时,选择一个 slave 升为主节点。
  4. 配置提供者(Configuration Provider):客户端连接 sentinel 来获取当前 master 地址。

故障转移步骤:

sequenceDiagram
    participant S1 as Sentinel-1
    participant Quorum as Quorum(n/2+1)
    participant Master as 原Master
    participant Slave as Slave节点

    S1->>Master: PING (超时判定主观下线)
    S1->>S1: 标记为 Subjectively Down (SDOWN)
    S1->>Quorum: 询问是否也认为 Master SDOWN
    Quorum-->>S1: n/2+1 确认
    S1->>S1: 标记为 Objectively Down (ODOWN)
    S1->>S1: 选举 leader Sentinel
    Note over S1: RAFT-like 选举<br/>先到先得
    S1->>Quorum: 投票选出 Failover Owner
    S1->>Slave: 选出最合适的 slave (复制偏移量最大)
    Slave->>Slave: SLAVEOF NO ONE
    Slave->>Slave: 提升为 Master
    S1->>其他 Slave: SLAVEOF new-master IP PORT
    S1->>Clients: 更新 master 地址

关键参数:

参数 默认值 含义
down-after-milliseconds 30000 主观下线的判断时间(ms)
failover-timeout 180000 故障转移超时(ms)
parallel-syncs 1 故障转移后同时同步的新 master 数量

Note

quorum = n/2 + 1 中的 n 是配置的 Sentinel 节点总数,不是存活节点数。如果配置了 3 个 Sentinel,需要至少 2 个认为 master SDOWN 才会触发 ODOWN。这意味着少数派宕机不影响多数派的故障检测。

Redis Cluster — 槽位分配

Redis Cluster 是无中心架构,每个节点都知道完整的拓扑结构。数据分片基于 哈希槽(hash slot)。

16384 个槽位:

  • Redis Cluster 将 16384 个 hash slot 分布在多个节点上
  • Key 的 slot 计算:CRC16(key) % 16384
  • 客户端可以通过 ASK 和 MOVED 重定向消息定位到正确节点

槽位迁移流程(在线迁移):

sequenceDiagram
    participant Admin as 管理员
    participant NodeA as Source Node
    participant NodeB as Target Node
    participant Client as 客户端

    Admin->>NodeA: CLUSTER ADDSLOTS 0-5460
    NodeA->>NodeB: 开始迁移 slots
    Note over NodeA,NodeB: 阶段1: IMPORTING
    NodeA->>NodeA: setslot IMPORTING <slot> <target-id>
    NodeB->>NodeB: setslot MIGRATING <slot> <source-id>
    loop 逐个 key 迁移
        NodeA->>NodeB: MIGRATE host port key timeout
        Note over NodeA: client 查 key → ASK redirect →<br/>再查到目标 node
    end
    NodeA->>NodeA: setslot NODE <my-id> (迁移完成)
    Client->>NodeB: 直接查找 (no redirect needed)

Gossip 协议

哨兵之间使用简单的主子 Gossip 协议进行信息交换:

  • PUBLISH/SUBSCRIBE:哨兵可以发布订阅频道获取全局事件
  • HEARTBEAT:每秒钟互相发送 PING,包含自身的版本号和当前 master 状态
  • INFO 传播:每个哨兵维护整个集群的视图,定期与其他哨兵同步
  • 领导选举:基于 Raft 思想的简化版——先到先得,获得 quorum 票数成为 owner

Warning

Gossip 协议存在最终一致性延迟。在哨兵刚刚选出新 leader 的瞬间,未收到通知的哨兵可能仍认为旧状态是正确的,此时如果再次发生故障转移请求可能出现混乱。实际生产中要合理设置 timeouts。

CP vs AP 权衡

Redis Cluster 在设计上做出了明确的取舍:

graph LR
    A["CAP 定理"] --> B{"一致性 or 可用性?"}
    B -->|Redis Cluster 选择| C["AP 倾向<br/>(可用性优先)"]
    B -->|对比方案| D["Redis Sentinel CP 倾向"]

    C --> E["分片节点不可用时<br/>部分操作返回 -CLUSTERDOWN"]
    D --> F["单主从切换期间<br/>短暂不可用但最终一致"]

    E --> G["适合:缓存/排行榜等可接受短暂不一致场景"]
    F --> H["适合:会话存储/限流计数等要求强一致场景"]
维度 Redis Sentinel Redis Cluster
一致性模型 CP(主从切换期间短暂不可用但保证一致) AP(分片后允许不同分区有不同视角)
数据分片 否(全量复制到每个从节点) 是(16384 槽位分散)
多 DB 支持 支持(DB0 ~ DB15) 不支持(仅 DB0)
扩容方式 手动迁移或重新搭建 在线增量迁移
适用场景 中小规模、强一致性要求 大规模、水平扩展需求

代码示例

Go 中使用 Redigo 连接 Sentinel:

import "github.com/gomodule/redigo/redis"

sentinelAddrs := []string{"192.168.1.10:26379", "192.168.1.11:26379"}
pool := &redis.Pool{
    MaxIdle:     10,
    DialContext: func(ctx context.Context) (conn redis.Conn, err error) {
        // 自动发现 master,无需硬编码地址
        conn, err = redis.DialSentinel(
            "mymaster",           // sentinel 网络名
            sentinelAddrs,        // sentinel 地址列表
            "", "",               // username/password
        )
        return
    },
}
defer pool.Close()
# Sentinel CLI 查看当前 master 信息
127.0.0.1:26379> SENTINEL get-master-addr-by-name mymaster
1) "192.168.1.10"
2) "6379"             # 如果返回空数组,说明正在故障转移中

实践场景

  1. Sentinel 部署最佳实践:至少 3 个(奇数),跨机房部署避免单机房断电导致全部失联。quorum 设为 n/2 + 1。

  2. 客户端感知的 Sentinel 连接:Java 的 Jedis / Lettuce 和 Go 的 redigo 都内置了 Sentinel 发现逻辑,配置好 master name 即可自动路由。不要把 master IP 写死在配置里。

  3. Cluster 扩缩容:新增节点时先 CLUSTER MEET 加入集群,再通过 CLUSTER ADDSLOT 分配槽位,最后用 redis-cli --cluster rebalance 自动均衡。整个过程业务零停机。

  4. 脑裂风险:当网络和物理隔离导致两个"主"共存时,会产生数据分裂。可通过 min-replicas-to-write 和 min-replicas-max-lag 降低风险。

关联笔记