7.8 KiB
tags, create time, update time
| tags | create time | update time | |||||
|---|---|---|---|---|---|---|---|
|
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
四大职责:
- 监控(Monitoring):定期检查 Master、Slave 和其他 Sentinel 是否可达(通过 PING)。
- 提醒(Notification):当某个节点发现异常时,通知其他 Sentinel。
- 自动故障转移(Failover):当 master 被标记为客观下线(ODOWN)时,选择一个 slave 升为主节点。
- 配置提供者(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" # 如果返回空数组,说明正在故障转移中
实践场景
-
Sentinel 部署最佳实践:至少 3 个(奇数),跨机房部署避免单机房断电导致全部失联。quorum 设为 n/2 + 1。
-
客户端感知的 Sentinel 连接:Java 的 Jedis / Lettuce 和 Go 的 redigo 都内置了 Sentinel 发现逻辑,配置好 master name 即可自动路由。不要把 master IP 写死在配置里。
-
Cluster 扩缩容:新增节点时先
CLUSTER MEET加入集群,再通过CLUSTER ADDSLOT分配槽位,最后用redis-cli --cluster rebalance自动均衡。整个过程业务零停机。 -
脑裂风险:当网络和物理隔离导致两个"主"共存时,会产生数据分裂。可通过
min-replicas-to-write和min-replicas-max-lag降低风险。