2056 字
10 分钟
Redis 高可用详解(2)集群模式
本文为 Redis 高可用架构 子篇,单独讲解 Redis 集群模式。
Redis 集群模式
Redis Cluster 是官方的分片 + 高可用方案:数据按槽位分散到多个主节点,每个主节点可带从节点,集群自己完成故障检测与故障转移。
一个最小生产集群:3 主 3 从,16384 个槽均分到 3 个主节点,每个主节点在自己的从节点上有一份副本。
flowchart TB
subgraph CL["客户端"]
APP["应用"]
end
subgraph SHARD1["分片1 槽0-5460"]
M1[(master-1)]
S1[(replica-1)]
M1 == "异步复制" ==> S1
end
subgraph SHARD2["分片2 槽5461-10922"]
M2[(master-2)]
S2[(replica-2)]
M2 == "异步复制" ==> S2
end
subgraph SHARD3["分片3 槽10923-16383"]
M3[(master-3)]
S3[(replica-3)]
M3 == "异步复制" ==> S3
end
APP -- "CRC16 mod 16384=5461 → 分片2" --> M2
M1 <-. "gossip" .-> M2
M2 <-. "gossip" .-> M3
M3 <-. "gossip" .-> M1
M1 -. "跨槽请求 → -MOVED 5461 10.0.0.12:6379" .-> APP
M2 -. "槽迁移中 → -ASK" .-> APP
classDef master fill:#fde2e4,stroke:#c9184a,stroke-width:2px
classDef replica fill:#e2ece9,stroke:#2d6a4f
class M1,M2,M3 master
class S1,S2,S3 replica读图要点:
- 槽是分片的最小单位:客户端先算
CRC16(key) mod 16384得到槽,再由本地映射表找到负责该槽的主节点。3 个主节点各分到约 5461 个槽。 - MOVED 与 ASK 是”纠错”而非”代理”:节点不会代转命令,只返回重定向错误,因此客户端必须支持 Cluster(否则直接用不了)。
- 客户端处理流程:本地缓存 键→槽→节点 映射,遇 MOVED 刷新,遇 ASK 单次重定向。
- gossip 让集群去中心化:没有哨兵这样的独立控制面,节点之间互探、互投,故障检测(
cluster-node-timeout)与选举投票都走 gossip。 - 每个分片都是一主多从:主节点故障时由从节点发起选举并接管槽位;若某个主节点没有从节点,它挂掉后这些槽无人接管,配合默认的
cluster-require-full-coverage yes会导致整个集群拒绝服务。 - 复制依然是异步的:切换同样可能丢”未同步的写入”。
数据分片与槽位
| 概念 | 说明 |
|---|---|
| 哈希槽(hash slot) | 固定 16384 个槽,集群的最小分配单位 |
| 键 → 槽 | CRC16(key) mod 16384,客户端与集群用同一套算法 |
| 槽 → 节点 | 建集群时分配(redis-cli --cluster create),可在线迁移(resharding) |
| 单键命令限制 | 一个命令涉及的所有 key 必须在同一槽,否则报 CROSSSLOT |
| Hash Tag | 用 {} 指定参与哈希的部分:user:{1001}:name 与 user:{1001}:age 落在同槽,这是让多键操作合法的唯一手段 |
# 建集群:3 主 3 从(--cluster-replicas 1 表示每个主配 1 个从)redis-cli --cluster create \ 10.0.0.11:6379 10.0.0.12:6379 10.0.0.13:6379 \ 10.0.0.14:6379 10.0.0.15:6379 10.0.0.16:6379 \ --cluster-replicas 1
redis-cli --cluster info 10.0.0.11:6379 # 集群概览redis-cli --cluster check 10.0.0.11:6379 # 体检:槽覆盖、配置纪元、主从关系redis-cli -c -h 10.0.0.11 CLUSTER NODES # 每个节点的角色、槽范围、flags重定向:MOVED 与 ASK
客户端把命令发错节点时,节点不会代转,而是返回重定向错误:
| 返回 | 含义 | 客户端动作 |
|---|---|---|
-MOVED 3999 10.0.0.12:6379 | 该槽已经永久属于另一个节点 | 更新本地槽位映射表,然后重发到目标节点 |
-ASK 3999 10.0.0.12:6379 | 该槽正在迁移,这个 key 已搬到目标节点 | 只对本次请求发 ASKING + 命令到目标节点,不更新本地映射 |
理解要点:MOVED 是”搬家完成”,ASK 是”搬家进行中”。支持集群的客户端会对槽映射做缓存,所以正常运行时重定向很少;一旦频繁出现 MOVED,通常说明客户端没缓存好映射,或集群刚做过 resharding。
扩容与缩容(resharding)
# 加入新节点(先以空主节点加入,再重新分配槽)redis-cli --cluster add-node 10.0.0.17:6379 10.0.0.11:6379# 让新节点成为某主节点的从节点redis-cli --cluster add-node 10.0.0.18:6379 10.0.0.11:6379 --cluster-slave --cluster-master-id <node-id>
# 迁移槽:交互式(会提示迁移多少个槽、从哪些节点迁)redis-cli --cluster reshard 10.0.0.11:6379# 非交互:把 1000 个槽从 source 迁到 targetredis-cli --cluster reshard 10.0.0.11:6379 \ --cluster-from <source-id> --cluster-to <target-id> \ --cluster-slots 1000 --cluster-yes
# 删除节点(先从集群里移除空节点,缩容前要先把槽迁走)redis-cli --cluster del-node 10.0.0.11:6379 <node-id>在线迁移单个槽的过程(对应 ASK 的来源):
- 目标节点执行
CLUSTER SETSLOT <slot> IMPORTING <source-id>; - 源节点执行
CLUSTER SETSLOT <slot> MIGRATING <target-id>; - 源节点对该槽内的 key 逐个
MIGRATE到目标节点(迁移期间该槽的请求:key 还在源 → 正常处理;key 已迁走 → 返回 ASK); - 全部 key 迁完后,向所有节点广播
CLUSTER SETSLOT <slot> NODE <target-id>,迁移完成,此后变为 MOVED。
集群的故障转移
| 步骤 | 说明 |
|---|---|
| 1. 主观下线(PFAIL) | 节点在 cluster-node-timeout 内未收到某节点的 PONG,把它标为 PFAIL |
| 2. 客观下线(FAIL) | 节点通过 gossip 收集其他节点的判断,当超过半数主节点也认为该节点 PFAIL,则广播 FAIL |
| 3. 从节点发起选举 | 故障主节点的从节点在延迟 (rank * 1s) 后发起选举请求(rank 与数据新旧相关,数据越新的从节点越早发起) |
| 4. 主节点投票 | 持有槽的主节点投票,每个主节点在每个纪元(epoch)只能投一票,得票需超过半数持有槽的主节点 |
| 5. 提升与接管 | 当选从节点执行 CLUSTER FAILOVER 的等价动作,提升为主、接管槽位,并广播新的 configEpoch |
| 6. 客户端更新映射 | 客户端收到 MOVED 或通过 CLUSTER SLOTS/CLUSTER SHARDS 刷新槽映射 |
sequenceDiagram
autonumber
participant CS as 客户端
participant M1 as master-1<br/>(故障)
participant MR as 其他持槽主节点<br/>master-2 / master-3
participant S1 as replica-1
Note over M1,S1: 阶段一:故障检测(经 gossip 传播)
MR->>M1: PING,cluster-node-timeout 内无 PONG
MR->>MR: 标记 M1 为 PFAIL(主观下线)
MR->>S1: gossip 广播"我认为 M1 PFAIL",互相确认
Note over MR,S1: 认为 PFAIL 的持槽主节点数 > 半数<br/>→ 广播 FAIL(客观下线达成)
Note over MR,S1: 阶段二:从节点自荐 + 主节点投票
activate S1
S1->>S1: 延迟 rank × 1s 后发起选举
S1->>MR: FAILOVER_AUTH_REQUEST(携带 currentEpoch)
MR->>S1: FAILOVER_AUTH_ACK(每个主节点每纪元仅一票)
Note over S1: 得票 > 半数持槽主节点 → 当选
deactivate S1
Note over CS,S1: 阶段三:接管槽位与客户端纠正
activate S1
S1->>S1: 提升为 master,接管 M1 的槽<br/>并广播新 configEpoch
CS->>M1: 请求 M1 的槽(客户端映射已过期)
M1-->>CS: -MOVED slot 10.0.0.14:6379
CS->>S1: 重发到新主
S1-->>CS: 正常响应,客户端刷新槽映射
deactivate S1# 手工触发故障转移(演练用)redis-cli -h 10.0.0.14 -p 6379 CLUSTER FAILOVER # 在当前从节点上执行redis-cli -h 10.0.0.14 -p 6379 CLUSTER FAILOVER TAKEOVER # 无需与主节点协商,强制接管(危险)
# 观察redis-cli --cluster check 10.0.0.11:6379redis-cli -h 10.0.0.11 CLUSTER INFO # cluster_state:ok / cluster_slots_assigned 等什么情况下集群会整体不可用?
| 情况 | 结果 |
|---|---|
| 某主节点故障且没有从节点 | 该主节点的槽无人接管,集群状态变为 fail,整个集群拒绝所有请求(CLUSTERDOWN),不只是这部分数据不可用 |
| 故障主节点的从节点也全挂 | 同上 |
| 多数主节点不可达 | 无法形成多数派,故障转移无法完成,集群不可用 |
cluster-require-full-coverage yes(默认) | 只要有槽未覆盖就整体拒绝服务;设为 no 则只影响未覆盖的槽(牺牲一致性换可用性) |
集群通信与限制
- gossip 协议:节点之间用
CLUSTER MEET建立连接后,通过 gossip 消息周期性交换”节点状态 + 槽位分配 + 纪元”,实现去中心化的故障检测与配置传播。集群内部总线端口 = 客户端端口 + 10000(cluster-port可改),防火墙要同时放通两个端口。 cluster-node-timeout:故障判定门槛。设太小会因网络抖动误判切换,太大会拖长 RTO。跨机房部署需显著调大(例如 5~15s)。- 跨槽限制:
MGET、MSET、事务(MULTI)、Lua 脚本涉及的 key 必须在同一槽;跨槽需要客户端拆分或使用 hash tag 归并。 - 不支持
SELECT多库:集群只有 db0。 KEYS/SCAN只作用于当前节点:全量遍历必须遍历所有主节点,FLUSHALL也只在单节点生效。- 主从仍是异步复制:切换同样会丢”未同步的写入”,
WAIT依然可用。 - 客户端必须支持集群:Jedis/Lettuce/Redisson/go-redis 等主流客户端都支持,但要注意连接池与重定向处理配置。
什么时候不该用 Cluster?
| 场景 | 建议 |
|---|---|
| 单实例内存足够 | 主从 + 哨兵,运维复杂度低得多 |
| 需要跨槽事务 / 频繁多键操作 | 用 hash tag 归并,或改用客户端分片(Codis/Twemproxy,但需接受其局限)或代理方案 |
需要 KEYS/SCAN 全量遍历 | 客户端遍历各节点,或另建离线索引 |
| 希望”分片对应用透明” | 考虑 Redis 协议兼容的分布式方案(如 Tendis、云厂商的集群代理模式) |
Redis 高可用详解(2)集群模式
https://blog-l7wd3.pages.dev/posts/ha/redis-ha-cluster/分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 39 天
部分内容可能已过时