Redis 从入门到精通(六):集群模式(Cluster)—— 分布式架构、哈希槽与 Gossip 协议全解

Redis 从入门到精通(六):集群模式(Cluster)------ 分布式架构、哈希槽与 Gossip 协议全解


一、为什么需要 Cluster?从单机到分片的演进

1.1 单机 → 主从 → Sentinel → Cluster 的演进路径

#mermaid-svg-G9NC7JOagu7wtqU7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G9NC7JOagu7wtqU7 .error-icon{fill:#552222;}#mermaid-svg-G9NC7JOagu7wtqU7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G9NC7JOagu7wtqU7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G9NC7JOagu7wtqU7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G9NC7JOagu7wtqU7 .marker.cross{stroke:#333333;}#mermaid-svg-G9NC7JOagu7wtqU7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G9NC7JOagu7wtqU7 p{margin:0;}#mermaid-svg-G9NC7JOagu7wtqU7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster-label text{fill:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster-label span{color:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster-label span p{background-color:transparent;}#mermaid-svg-G9NC7JOagu7wtqU7 .label text,#mermaid-svg-G9NC7JOagu7wtqU7 span{fill:#333;color:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 .node rect,#mermaid-svg-G9NC7JOagu7wtqU7 .node circle,#mermaid-svg-G9NC7JOagu7wtqU7 .node ellipse,#mermaid-svg-G9NC7JOagu7wtqU7 .node polygon,#mermaid-svg-G9NC7JOagu7wtqU7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G9NC7JOagu7wtqU7 .rough-node .label text,#mermaid-svg-G9NC7JOagu7wtqU7 .node .label text,#mermaid-svg-G9NC7JOagu7wtqU7 .image-shape .label,#mermaid-svg-G9NC7JOagu7wtqU7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-G9NC7JOagu7wtqU7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G9NC7JOagu7wtqU7 .rough-node .label,#mermaid-svg-G9NC7JOagu7wtqU7 .node .label,#mermaid-svg-G9NC7JOagu7wtqU7 .image-shape .label,#mermaid-svg-G9NC7JOagu7wtqU7 .icon-shape .label{text-align:center;}#mermaid-svg-G9NC7JOagu7wtqU7 .node.clickable{cursor:pointer;}#mermaid-svg-G9NC7JOagu7wtqU7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G9NC7JOagu7wtqU7 .arrowheadPath{fill:#333333;}#mermaid-svg-G9NC7JOagu7wtqU7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G9NC7JOagu7wtqU7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G9NC7JOagu7wtqU7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9NC7JOagu7wtqU7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G9NC7JOagu7wtqU7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9NC7JOagu7wtqU7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster text{fill:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 .cluster span{color:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G9NC7JOagu7wtqU7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G9NC7JOagu7wtqU7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-G9NC7JOagu7wtqU7 .icon-shape,#mermaid-svg-G9NC7JOagu7wtqU7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9NC7JOagu7wtqU7 .icon-shape p,#mermaid-svg-G9NC7JOagu7wtqU7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G9NC7JOagu7wtqU7 .icon-shape .label rect,#mermaid-svg-G9NC7JOagu7wtqU7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9NC7JOagu7wtqU7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G9NC7JOagu7wtqU7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G9NC7JOagu7wtqU7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 主从复制
Sentinel
Cluster
单机 Redis

容量=单机内存

无高可用
主从模式

读写分离

手动故障转移
Sentinel 高可用

自动故障转移

容量=单机内存
Redis Cluster

数据分片

去中心化

容量 = N × 单机内存

阶段 解决了什么 没解决什么
单机 能用 容量瓶颈、单点故障、读压力
主从 读写分离、多副本 容量瓶颈、手动切换
Sentinel 自动故障转移 容量瓶颈依然存在
Cluster 数据分片、水平扩展 复杂度提升、跨槽操作受限

一句话 :Sentinel 解决的是高可用问题,Cluster 解决的是**分布式(容量 + 高可用)**问题。

1.2 Cluster 的核心设计理念

#mermaid-svg-m6tMN0QZ2Q09VtlT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-m6tMN0QZ2Q09VtlT .error-icon{fill:#552222;}#mermaid-svg-m6tMN0QZ2Q09VtlT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-m6tMN0QZ2Q09VtlT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .marker.cross{stroke:#333333;}#mermaid-svg-m6tMN0QZ2Q09VtlT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-m6tMN0QZ2Q09VtlT p{margin:0;}#mermaid-svg-m6tMN0QZ2Q09VtlT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster-label text{fill:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster-label span{color:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster-label span p{background-color:transparent;}#mermaid-svg-m6tMN0QZ2Q09VtlT .label text,#mermaid-svg-m6tMN0QZ2Q09VtlT span{fill:#333;color:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .node rect,#mermaid-svg-m6tMN0QZ2Q09VtlT .node circle,#mermaid-svg-m6tMN0QZ2Q09VtlT .node ellipse,#mermaid-svg-m6tMN0QZ2Q09VtlT .node polygon,#mermaid-svg-m6tMN0QZ2Q09VtlT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .rough-node .label text,#mermaid-svg-m6tMN0QZ2Q09VtlT .node .label text,#mermaid-svg-m6tMN0QZ2Q09VtlT .image-shape .label,#mermaid-svg-m6tMN0QZ2Q09VtlT .icon-shape .label{text-anchor:middle;}#mermaid-svg-m6tMN0QZ2Q09VtlT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .rough-node .label,#mermaid-svg-m6tMN0QZ2Q09VtlT .node .label,#mermaid-svg-m6tMN0QZ2Q09VtlT .image-shape .label,#mermaid-svg-m6tMN0QZ2Q09VtlT .icon-shape .label{text-align:center;}#mermaid-svg-m6tMN0QZ2Q09VtlT .node.clickable{cursor:pointer;}#mermaid-svg-m6tMN0QZ2Q09VtlT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .arrowheadPath{fill:#333333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m6tMN0QZ2Q09VtlT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-m6tMN0QZ2Q09VtlT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m6tMN0QZ2Q09VtlT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster text{fill:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT .cluster span{color:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-m6tMN0QZ2Q09VtlT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-m6tMN0QZ2Q09VtlT rect.text{fill:none;stroke-width:0;}#mermaid-svg-m6tMN0QZ2Q09VtlT .icon-shape,#mermaid-svg-m6tMN0QZ2Q09VtlT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m6tMN0QZ2Q09VtlT .icon-shape p,#mermaid-svg-m6tMN0QZ2Q09VtlT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-m6tMN0QZ2Q09VtlT .icon-shape .label rect,#mermaid-svg-m6tMN0QZ2Q09VtlT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m6tMN0QZ2Q09VtlT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-m6tMN0QZ2Q09VtlT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-m6tMN0QZ2Q09VtlT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Redis Cluster 核心特性
去中心化

无代理层,客户端直连节点
16384 哈希槽

CRC16(key) % 16384
主从高可用

每个分片一个主 + 多个从
自动分片与迁移

在线扩容/缩容
Gossip 协议

节点间状态传播

  • 去中心化:没有中心代理节点,客户端直连任意集群节点,通过重定向找到正确的节点
  • 16384 个哈希槽(Hash Slot):数据分片的逻辑单位,每个节点负责一部分槽
  • 内置高可用:每个分片自带主从,主库挂了自动选举新主库(不需要额外的 Sentinel)
  • 线性扩展:增加节点即可扩展容量和吞吐量

二、哈希槽(Hash Slot):数据分片的核心

2.1 为什么是 16384?

Redis Cluster 没有使用一致性哈希,而是采用了哈希槽 。总共 16384(2^14) 个槽,每个 key 通过 CRC16(key) % 16384 计算落在哪个槽上。
#mermaid-svg-NOfcK2h5h0ef2Rju{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NOfcK2h5h0ef2Rju .error-icon{fill:#552222;}#mermaid-svg-NOfcK2h5h0ef2Rju .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NOfcK2h5h0ef2Rju .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NOfcK2h5h0ef2Rju .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NOfcK2h5h0ef2Rju .marker.cross{stroke:#333333;}#mermaid-svg-NOfcK2h5h0ef2Rju svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NOfcK2h5h0ef2Rju p{margin:0;}#mermaid-svg-NOfcK2h5h0ef2Rju .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster-label text{fill:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster-label span{color:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster-label span p{background-color:transparent;}#mermaid-svg-NOfcK2h5h0ef2Rju .label text,#mermaid-svg-NOfcK2h5h0ef2Rju span{fill:#333;color:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju .node rect,#mermaid-svg-NOfcK2h5h0ef2Rju .node circle,#mermaid-svg-NOfcK2h5h0ef2Rju .node ellipse,#mermaid-svg-NOfcK2h5h0ef2Rju .node polygon,#mermaid-svg-NOfcK2h5h0ef2Rju .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NOfcK2h5h0ef2Rju .rough-node .label text,#mermaid-svg-NOfcK2h5h0ef2Rju .node .label text,#mermaid-svg-NOfcK2h5h0ef2Rju .image-shape .label,#mermaid-svg-NOfcK2h5h0ef2Rju .icon-shape .label{text-anchor:middle;}#mermaid-svg-NOfcK2h5h0ef2Rju .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NOfcK2h5h0ef2Rju .rough-node .label,#mermaid-svg-NOfcK2h5h0ef2Rju .node .label,#mermaid-svg-NOfcK2h5h0ef2Rju .image-shape .label,#mermaid-svg-NOfcK2h5h0ef2Rju .icon-shape .label{text-align:center;}#mermaid-svg-NOfcK2h5h0ef2Rju .node.clickable{cursor:pointer;}#mermaid-svg-NOfcK2h5h0ef2Rju .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NOfcK2h5h0ef2Rju .arrowheadPath{fill:#333333;}#mermaid-svg-NOfcK2h5h0ef2Rju .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NOfcK2h5h0ef2Rju .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NOfcK2h5h0ef2Rju .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NOfcK2h5h0ef2Rju .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NOfcK2h5h0ef2Rju .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NOfcK2h5h0ef2Rju .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster text{fill:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju .cluster span{color:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NOfcK2h5h0ef2Rju .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NOfcK2h5h0ef2Rju rect.text{fill:none;stroke-width:0;}#mermaid-svg-NOfcK2h5h0ef2Rju .icon-shape,#mermaid-svg-NOfcK2h5h0ef2Rju .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NOfcK2h5h0ef2Rju .icon-shape p,#mermaid-svg-NOfcK2h5h0ef2Rju .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NOfcK2h5h0ef2Rju .icon-shape .label rect,#mermaid-svg-NOfcK2h5h0ef2Rju .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NOfcK2h5h0ef2Rju .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NOfcK2h5h0ef2Rju .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NOfcK2h5h0ef2Rju :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 集群节点
16384 个 Hash Slot
Slot 0
Slot 1
...
Slot 5460
Slot 5461
...
Slot 10922
Slot 10923
...
Slot 16383
Node A

Slot 0 ~ 5460

(5461 个)
Node B

Slot 5461 ~ 10922

(5462 个)
Node C

Slot 10923 ~ 16383

(5461 个)

为什么是 16384 而不是 65535? Redis 作者 antirez 给过解释:

原因 说明
心跳包大小 节点间 Gossip 消息中包含本节点的槽位图(bitmap),16384 个槽 = 2KB,65535 = 8KB。16384 刚好在 TCP 报文不拆包的前提下最大化
节点数预期 集群规模通常在 1000 以内,16384 个槽足够均匀分配
位图传输效率 16384 bit = 2048 字节,压缩后更小

2.2 Hash Tag:把多个 key 放到同一个槽

有时候我们需要多个 key 落在同一个节点上(比如事务、Lua 脚本需要操作多个 key)。Hash Tag 语法:

bash 复制代码
# 只用 {} 内的部分计算哈希
SET user:{1001}:name "张三"
SET user:{1001}:age 28
SET user:{1001}:email "zhangsan@example.com"

# 这三个 key 都只对 "1001" 做 CRC16,保证落在同一个槽

原理 :如果 key 中包含 {},Redis 只对 {} 中间的部分计算 CRC16:

复制代码
CRC16("user:{1001}:name")   → 只用 "1001" 计算
CRC16("user:{1001}:age")    → 只用 "1001" 计算  ← 同一槽
CRC16("user:{1001}:email")  → 只用 "1001" 计算  ← 同一槽

⚠️ 慎用 Hash Tag:过度使用会导致数据倾斜------大量 key 集中在一个槽,热点节点成为瓶颈。


三、MOVED 与 ASK 重定向:客户端如何找到正确的节点?

3.1 MOVED 重定向 ------ "你找错人了,去那边"

当你向任意一个集群节点发送命令,如果该节点不负责这个 key 对应的槽,它会返回 MOVED 错误:

bash 复制代码
# 客户端连接到 Node A(负责 0~5460)
redis-cli -c -h nodeA -p 6379 SET key_in_slot_10000 "value"

# Node A 返回:
-MOVED 10000 nodeC:6379
#       ↑     ↑
#       槽号   正确的节点地址

Node C(负责槽10923-16383) Node A(负责槽0-5460) 客户端 Node C(负责槽10923-16383) Node A(负责槽0-5460) 客户端 #mermaid-svg-bPWTV6UL2PSQAgRw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bPWTV6UL2PSQAgRw .error-icon{fill:#552222;}#mermaid-svg-bPWTV6UL2PSQAgRw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bPWTV6UL2PSQAgRw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bPWTV6UL2PSQAgRw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bPWTV6UL2PSQAgRw .marker.cross{stroke:#333333;}#mermaid-svg-bPWTV6UL2PSQAgRw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bPWTV6UL2PSQAgRw p{margin:0;}#mermaid-svg-bPWTV6UL2PSQAgRw .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bPWTV6UL2PSQAgRw text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bPWTV6UL2PSQAgRw .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-bPWTV6UL2PSQAgRw .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-bPWTV6UL2PSQAgRw #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-bPWTV6UL2PSQAgRw .sequenceNumber{fill:white;}#mermaid-svg-bPWTV6UL2PSQAgRw #sequencenumber{fill:#333;}#mermaid-svg-bPWTV6UL2PSQAgRw #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-bPWTV6UL2PSQAgRw .messageText{fill:#333;stroke:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bPWTV6UL2PSQAgRw .labelText,#mermaid-svg-bPWTV6UL2PSQAgRw .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .loopText,#mermaid-svg-bPWTV6UL2PSQAgRw .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bPWTV6UL2PSQAgRw .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-bPWTV6UL2PSQAgRw .noteText,#mermaid-svg-bPWTV6UL2PSQAgRw .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-bPWTV6UL2PSQAgRw .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bPWTV6UL2PSQAgRw .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bPWTV6UL2PSQAgRw .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bPWTV6UL2PSQAgRw .actorPopupMenu{position:absolute;}#mermaid-svg-bPWTV6UL2PSQAgRw .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-bPWTV6UL2PSQAgRw .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bPWTV6UL2PSQAgRw .actor-man circle,#mermaid-svg-bPWTV6UL2PSQAgRw line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-bPWTV6UL2PSQAgRw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SET key_in_slot_10000 "value"CRC16(key) % 16384 = 10000-MOVED 10000 NodeC:6379SET key_in_slot_10000 "value"OK

注意

  • MOVED 意味着永久重定向------这个槽确实不属于当前节点
  • 智能客户端(Jedis Cluster、Lettuce)会缓存槽位映射,下次相同的槽直接发到正确节点
  • redis-cli -c 开启了自动重定向模式,客户端自动跟随 MOVED

3.2 ASK 重定向 ------ "这个槽正在搬家,去那边试试"

当槽在迁移过程中(MIGRATING 状态),目标节点没有该 key 时会返回 ASK:

bash 复制代码
# 槽 10000 正在从 Node A 迁移到 Node C

# 客户端连接 Node A(源节点)
redis-cli -h nodeA -p 6379 GET key_in_slot_10000
# Node A:key 已经迁走了,返回
-ASK 10000 nodeC:6379

Node C(目标)槽 10000: IMPORTING Node A(源)槽 10000: MIGRATING 客户端 Node C(目标)槽 10000: IMPORTING Node A(源)槽 10000: MIGRATING 客户端 #mermaid-svg-Gud4pRn4Ct9PeDJi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Gud4pRn4Ct9PeDJi .error-icon{fill:#552222;}#mermaid-svg-Gud4pRn4Ct9PeDJi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Gud4pRn4Ct9PeDJi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Gud4pRn4Ct9PeDJi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Gud4pRn4Ct9PeDJi .marker.cross{stroke:#333333;}#mermaid-svg-Gud4pRn4Ct9PeDJi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Gud4pRn4Ct9PeDJi p{margin:0;}#mermaid-svg-Gud4pRn4Ct9PeDJi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Gud4pRn4Ct9PeDJi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Gud4pRn4Ct9PeDJi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Gud4pRn4Ct9PeDJi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Gud4pRn4Ct9PeDJi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Gud4pRn4Ct9PeDJi .sequenceNumber{fill:white;}#mermaid-svg-Gud4pRn4Ct9PeDJi #sequencenumber{fill:#333;}#mermaid-svg-Gud4pRn4Ct9PeDJi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Gud4pRn4Ct9PeDJi .messageText{fill:#333;stroke:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Gud4pRn4Ct9PeDJi .labelText,#mermaid-svg-Gud4pRn4Ct9PeDJi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .loopText,#mermaid-svg-Gud4pRn4Ct9PeDJi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Gud4pRn4Ct9PeDJi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Gud4pRn4Ct9PeDJi .noteText,#mermaid-svg-Gud4pRn4Ct9PeDJi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Gud4pRn4Ct9PeDJi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Gud4pRn4Ct9PeDJi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Gud4pRn4Ct9PeDJi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Gud4pRn4Ct9PeDJi .actorPopupMenu{position:absolute;}#mermaid-svg-Gud4pRn4Ct9PeDJi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Gud4pRn4Ct9PeDJi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Gud4pRn4Ct9PeDJi .actor-man circle,#mermaid-svg-Gud4pRn4Ct9PeDJi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Gud4pRn4Ct9PeDJi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} key_x 已经迁到 Node C GET key_x-ASK 10000 NodeC:6379ASKING(告知:我被 ASK 过来的,允许查 IMPORTING 槽)GET key_x"value"

3.3 MOVED vs ASK 对比

维度 MOVED ASK
含义 槽永久归属其他节点 槽正在迁移,临时的
触发时机 任何时候,槽不属于当前节点 槽在 MIGRATING/IMPORTING 状态
客户端行为 更新槽位缓存,下次直接发送 不更新缓存,下次仍发原节点(临时性)
特殊前置 必须先发送 ASKING 命令
迁移完成后 新节点正常响应 ASK 消失,迁移期间的 key 归新节点

四、Gossip 协议:去中心化的节点通信

4.1 为什么用 Gossip?

Redis Cluster 是去中心化的,没有 ZooKeeper 那样的协调中心。节点之间需要知道:

  • 哪些节点在线/离线
  • 每个节点负责哪些槽
  • 主从关系是怎样的
  • 哪个节点被选为新的主库

Gossip 协议 就是为这种场景量身定做的------每个节点随机挑选几个其他节点,定期交换信息,最终整个集群达到一致状态

4.2 Gossip 消息类型

Redis Cluster 的节点通信端口 = 服务端口 + 10000(如 6379 → 16379)。
#mermaid-svg-tqocvDac5MJuZsso{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tqocvDac5MJuZsso .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tqocvDac5MJuZsso .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tqocvDac5MJuZsso .error-icon{fill:#552222;}#mermaid-svg-tqocvDac5MJuZsso .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tqocvDac5MJuZsso .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tqocvDac5MJuZsso .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tqocvDac5MJuZsso .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tqocvDac5MJuZsso .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tqocvDac5MJuZsso .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tqocvDac5MJuZsso .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tqocvDac5MJuZsso .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tqocvDac5MJuZsso .marker.cross{stroke:#333333;}#mermaid-svg-tqocvDac5MJuZsso svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tqocvDac5MJuZsso p{margin:0;}#mermaid-svg-tqocvDac5MJuZsso .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tqocvDac5MJuZsso .cluster-label text{fill:#333;}#mermaid-svg-tqocvDac5MJuZsso .cluster-label span{color:#333;}#mermaid-svg-tqocvDac5MJuZsso .cluster-label span p{background-color:transparent;}#mermaid-svg-tqocvDac5MJuZsso .label text,#mermaid-svg-tqocvDac5MJuZsso span{fill:#333;color:#333;}#mermaid-svg-tqocvDac5MJuZsso .node rect,#mermaid-svg-tqocvDac5MJuZsso .node circle,#mermaid-svg-tqocvDac5MJuZsso .node ellipse,#mermaid-svg-tqocvDac5MJuZsso .node polygon,#mermaid-svg-tqocvDac5MJuZsso .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tqocvDac5MJuZsso .rough-node .label text,#mermaid-svg-tqocvDac5MJuZsso .node .label text,#mermaid-svg-tqocvDac5MJuZsso .image-shape .label,#mermaid-svg-tqocvDac5MJuZsso .icon-shape .label{text-anchor:middle;}#mermaid-svg-tqocvDac5MJuZsso .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tqocvDac5MJuZsso .rough-node .label,#mermaid-svg-tqocvDac5MJuZsso .node .label,#mermaid-svg-tqocvDac5MJuZsso .image-shape .label,#mermaid-svg-tqocvDac5MJuZsso .icon-shape .label{text-align:center;}#mermaid-svg-tqocvDac5MJuZsso .node.clickable{cursor:pointer;}#mermaid-svg-tqocvDac5MJuZsso .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tqocvDac5MJuZsso .arrowheadPath{fill:#333333;}#mermaid-svg-tqocvDac5MJuZsso .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tqocvDac5MJuZsso .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tqocvDac5MJuZsso .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tqocvDac5MJuZsso .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tqocvDac5MJuZsso .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tqocvDac5MJuZsso .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tqocvDac5MJuZsso .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tqocvDac5MJuZsso .cluster text{fill:#333;}#mermaid-svg-tqocvDac5MJuZsso .cluster span{color:#333;}#mermaid-svg-tqocvDac5MJuZsso div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tqocvDac5MJuZsso .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tqocvDac5MJuZsso rect.text{fill:none;stroke-width:0;}#mermaid-svg-tqocvDac5MJuZsso .icon-shape,#mermaid-svg-tqocvDac5MJuZsso .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tqocvDac5MJuZsso .icon-shape p,#mermaid-svg-tqocvDac5MJuZsso .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tqocvDac5MJuZsso .icon-shape .label rect,#mermaid-svg-tqocvDac5MJuZsso .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tqocvDac5MJuZsso .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tqocvDac5MJuZsso .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tqocvDac5MJuZsso :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Gossip 消息类型
MEET

邀请新节点加入集群
PING

心跳 + 交换状态
PONG

回复 PING / MEET
FAIL

广播某节点已故障
PUBLISH

Pub/Sub 消息传播
UPDATE

通知节点更新配置

4.3 PING/PONG 的详细工作流程

渲染错误: Mermaid 渲染失败: Parse error on line 18: ... Note over A,B,C: 通过反复的 PING/PONG ----------------------^ Expecting 'TXT', got ','

PING 的频率

  • 每秒钟随机选 5 个节点发送 PING
  • 如果某个节点超过 cluster-node-timeout / 2(默认 7.5 秒)没收到 PONG,立即发送 PING
  • 每个 PING 包的 Gossip 体包含 3 个随机节点的状态信息

4.4 Gossip 的优缺点

优点 缺点
去中心化,无单点故障 最终一致性,有收敛延迟
扩展性好,节点增加不影响架构 消息冗余(同一信息可能被多次传递)
实现简单,维护成本低 大规模集群下消息量 O(N × 节点数)
对网络分区有自愈能力 故障检测依赖于超时判断,不是即时的

五、Cluster 的故障检测与自动故障转移

5.1 故障检测:PFAIL → FAIL

Redis Cluster 的故障检测也是两步判断,和 Sentinel 的 SDOWN/ODOWN 如出一辙:
#mermaid-svg-sN36WpU5DUJaBxUI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sN36WpU5DUJaBxUI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sN36WpU5DUJaBxUI .error-icon{fill:#552222;}#mermaid-svg-sN36WpU5DUJaBxUI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sN36WpU5DUJaBxUI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sN36WpU5DUJaBxUI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sN36WpU5DUJaBxUI .marker.cross{stroke:#333333;}#mermaid-svg-sN36WpU5DUJaBxUI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sN36WpU5DUJaBxUI p{margin:0;}#mermaid-svg-sN36WpU5DUJaBxUI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sN36WpU5DUJaBxUI .cluster-label text{fill:#333;}#mermaid-svg-sN36WpU5DUJaBxUI .cluster-label span{color:#333;}#mermaid-svg-sN36WpU5DUJaBxUI .cluster-label span p{background-color:transparent;}#mermaid-svg-sN36WpU5DUJaBxUI .label text,#mermaid-svg-sN36WpU5DUJaBxUI span{fill:#333;color:#333;}#mermaid-svg-sN36WpU5DUJaBxUI .node rect,#mermaid-svg-sN36WpU5DUJaBxUI .node circle,#mermaid-svg-sN36WpU5DUJaBxUI .node ellipse,#mermaid-svg-sN36WpU5DUJaBxUI .node polygon,#mermaid-svg-sN36WpU5DUJaBxUI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sN36WpU5DUJaBxUI .rough-node .label text,#mermaid-svg-sN36WpU5DUJaBxUI .node .label text,#mermaid-svg-sN36WpU5DUJaBxUI .image-shape .label,#mermaid-svg-sN36WpU5DUJaBxUI .icon-shape .label{text-anchor:middle;}#mermaid-svg-sN36WpU5DUJaBxUI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sN36WpU5DUJaBxUI .rough-node .label,#mermaid-svg-sN36WpU5DUJaBxUI .node .label,#mermaid-svg-sN36WpU5DUJaBxUI .image-shape .label,#mermaid-svg-sN36WpU5DUJaBxUI .icon-shape .label{text-align:center;}#mermaid-svg-sN36WpU5DUJaBxUI .node.clickable{cursor:pointer;}#mermaid-svg-sN36WpU5DUJaBxUI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sN36WpU5DUJaBxUI .arrowheadPath{fill:#333333;}#mermaid-svg-sN36WpU5DUJaBxUI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sN36WpU5DUJaBxUI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sN36WpU5DUJaBxUI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sN36WpU5DUJaBxUI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sN36WpU5DUJaBxUI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sN36WpU5DUJaBxUI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sN36WpU5DUJaBxUI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sN36WpU5DUJaBxUI .cluster text{fill:#333;}#mermaid-svg-sN36WpU5DUJaBxUI .cluster span{color:#333;}#mermaid-svg-sN36WpU5DUJaBxUI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sN36WpU5DUJaBxUI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sN36WpU5DUJaBxUI rect.text{fill:none;stroke-width:0;}#mermaid-svg-sN36WpU5DUJaBxUI .icon-shape,#mermaid-svg-sN36WpU5DUJaBxUI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sN36WpU5DUJaBxUI .icon-shape p,#mermaid-svg-sN36WpU5DUJaBxUI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sN36WpU5DUJaBxUI .icon-shape .label rect,#mermaid-svg-sN36WpU5DUJaBxUI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sN36WpU5DUJaBxUI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sN36WpU5DUJaBxUI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sN36WpU5DUJaBxUI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

≥ 半数主节点
不足半数
Node A 向 Node B 发 PING
超时?

cluster-node-timeout
Node A 标记 Node B 为 PFAIL

(Possible Failure,疑似下线)
正常
Node A 通过 Gossip

向其他节点传播 PFAIL 信息
多少节点认为 B 是 PFAIL?
Node A 将 B 标记为 FAIL

并广播 FAIL 消息
等待更多反馈
触发故障转移

PFAIL(疑似下线):单个节点的判断,相当于 Sentinel 的 SDOWN。

FAIL(确认下线):多个主节点达成共识后的判断,相当于 Sentinel 的 ODOWN。

5.2 自动故障转移(Failover)

Cluster 的故障转移流程与 Sentinel 类似,但不需要独立的 Sentinel 进程------集群中的其他主节点充当了 Sentinel 的角色。
集群 故障主库的从库 其他主节点 集群 故障主库的从库 其他主节点 #mermaid-svg-n9RMxM4SOIEeadc3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n9RMxM4SOIEeadc3 .error-icon{fill:#552222;}#mermaid-svg-n9RMxM4SOIEeadc3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n9RMxM4SOIEeadc3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n9RMxM4SOIEeadc3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n9RMxM4SOIEeadc3 .marker.cross{stroke:#333333;}#mermaid-svg-n9RMxM4SOIEeadc3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n9RMxM4SOIEeadc3 p{margin:0;}#mermaid-svg-n9RMxM4SOIEeadc3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n9RMxM4SOIEeadc3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-n9RMxM4SOIEeadc3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-n9RMxM4SOIEeadc3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-n9RMxM4SOIEeadc3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-n9RMxM4SOIEeadc3 .sequenceNumber{fill:white;}#mermaid-svg-n9RMxM4SOIEeadc3 #sequencenumber{fill:#333;}#mermaid-svg-n9RMxM4SOIEeadc3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-n9RMxM4SOIEeadc3 .messageText{fill:#333;stroke:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n9RMxM4SOIEeadc3 .labelText,#mermaid-svg-n9RMxM4SOIEeadc3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .loopText,#mermaid-svg-n9RMxM4SOIEeadc3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-n9RMxM4SOIEeadc3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-n9RMxM4SOIEeadc3 .noteText,#mermaid-svg-n9RMxM4SOIEeadc3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-n9RMxM4SOIEeadc3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n9RMxM4SOIEeadc3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n9RMxM4SOIEeadc3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-n9RMxM4SOIEeadc3 .actorPopupMenu{position:absolute;}#mermaid-svg-n9RMxM4SOIEeadc3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-n9RMxM4SOIEeadc3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-n9RMxM4SOIEeadc3 .actor-man circle,#mermaid-svg-n9RMxM4SOIEeadc3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-n9RMxM4SOIEeadc3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 某主库被标记为 FAIL 发现自己主库 FAIL等待 cluster-node-timeout × 2发起选举(请求投票)在当前纪元中是否已投票?从库的数据是否足够新(offset 差距是否在可接受范围)同意投票获得 ≥ N/2+1 票升级为主库广播新配置接管原主库的槽通知其他从库指向新主库

选举规则

  1. 故障主库的所有从库都有资格参选
  2. 从库发现主库 FAIL 后,等待 500ms + 随机(0~500ms) + rank × 1000ms 后发起选举
  3. rank 越低越优先------rank 是根据从库的数据复制偏移量计算的,数据越新 rank 越小
  4. 获得过半数主节点投票的从库胜出
  5. 如果没人胜出,下一个纪元继续

5.3 集群与 Sentinel 故障转移对比

维度 Sentinel Cluster
监控者 独立 Sentinel 进程 集群中其他主节点
额外部署 需要 Sentinel 集群(3台+) 不需要,内建能力
故障判定 SDOWN → ODOWN(quorum) PFAIL → FAIL(半数主节点)
选主逻辑 replica-priority > offset > runid offset 越新 rank 越小
注册中心 Sentinel 充当 任何集群节点都能返回槽位信息

六、集群扩容与缩容:在线迁移实战

6.1 扩容流程

假设当前集群有 3 个主节点,需要扩容到 4 个:
#mermaid-svg-PKL4wb3ir7ZpmajA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PKL4wb3ir7ZpmajA .error-icon{fill:#552222;}#mermaid-svg-PKL4wb3ir7ZpmajA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PKL4wb3ir7ZpmajA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PKL4wb3ir7ZpmajA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PKL4wb3ir7ZpmajA .marker.cross{stroke:#333333;}#mermaid-svg-PKL4wb3ir7ZpmajA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PKL4wb3ir7ZpmajA p{margin:0;}#mermaid-svg-PKL4wb3ir7ZpmajA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster-label text{fill:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster-label span{color:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster-label span p{background-color:transparent;}#mermaid-svg-PKL4wb3ir7ZpmajA .label text,#mermaid-svg-PKL4wb3ir7ZpmajA span{fill:#333;color:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA .node rect,#mermaid-svg-PKL4wb3ir7ZpmajA .node circle,#mermaid-svg-PKL4wb3ir7ZpmajA .node ellipse,#mermaid-svg-PKL4wb3ir7ZpmajA .node polygon,#mermaid-svg-PKL4wb3ir7ZpmajA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PKL4wb3ir7ZpmajA .rough-node .label text,#mermaid-svg-PKL4wb3ir7ZpmajA .node .label text,#mermaid-svg-PKL4wb3ir7ZpmajA .image-shape .label,#mermaid-svg-PKL4wb3ir7ZpmajA .icon-shape .label{text-anchor:middle;}#mermaid-svg-PKL4wb3ir7ZpmajA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PKL4wb3ir7ZpmajA .rough-node .label,#mermaid-svg-PKL4wb3ir7ZpmajA .node .label,#mermaid-svg-PKL4wb3ir7ZpmajA .image-shape .label,#mermaid-svg-PKL4wb3ir7ZpmajA .icon-shape .label{text-align:center;}#mermaid-svg-PKL4wb3ir7ZpmajA .node.clickable{cursor:pointer;}#mermaid-svg-PKL4wb3ir7ZpmajA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PKL4wb3ir7ZpmajA .arrowheadPath{fill:#333333;}#mermaid-svg-PKL4wb3ir7ZpmajA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PKL4wb3ir7ZpmajA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PKL4wb3ir7ZpmajA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PKL4wb3ir7ZpmajA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PKL4wb3ir7ZpmajA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PKL4wb3ir7ZpmajA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster text{fill:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA .cluster span{color:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PKL4wb3ir7ZpmajA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PKL4wb3ir7ZpmajA rect.text{fill:none;stroke-width:0;}#mermaid-svg-PKL4wb3ir7ZpmajA .icon-shape,#mermaid-svg-PKL4wb3ir7ZpmajA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PKL4wb3ir7ZpmajA .icon-shape p,#mermaid-svg-PKL4wb3ir7ZpmajA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PKL4wb3ir7ZpmajA .icon-shape .label rect,#mermaid-svg-PKL4wb3ir7ZpmajA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PKL4wb3ir7ZpmajA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PKL4wb3ir7ZpmajA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PKL4wb3ir7ZpmajA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Step 3: 为新节点添加从库(可选)
redis-cli --cluster add-node

从库IP:6380 主库IP:6379 --cluster-slave
Step 2: 重新分配槽
redis-cli --cluster reshard

从每个旧节点迁移部分槽到新节点
3 个节点 × 16384 槽 ÷ 4 = 每节点 4096 槽
每个旧节点迁出 ~1365 槽
Step 1: 新节点加入集群
redis-cli --cluster add-node

新节点IP:6379 集群中任一节点IP:6379

实际操作命令

bash 复制代码
# 1. 添加新主节点到集群
redis-cli --cluster add-node 192.168.1.104:6379 192.168.1.101:6379
#                               ↑ 新节点              ↑ 集群中任意节点

# 2. 重新分片(会进入交互模式)
redis-cli --cluster reshard 192.168.1.101:6379

# 交互提示:
# How many slots do you want to move? 4096
# What is the receiving node ID? <新节点的 node-id>
# Source node #1: all (从所有已有节点平均迁出)
# Source node #2: done

# 3. 检查迁移进度
redis-cli --cluster check 192.168.1.101:6379

# 4. 添加从库
redis-cli --cluster add-node 192.168.1.104:6380 192.168.1.104:6379 --cluster-slave

6.2 槽迁移的底层过程

单个槽的迁移过程(同步完成):

复制代码
源节点状态变化:STABLE → MIGRATING(准备迁出)
目标节点状态变化:STABLE → IMPORTING(准备迁入)

迁移每个 key 的步骤:
1. 源节点 DUMP key → 序列化
2. 目标节点 RESTORE key → 反序列化
3. 源节点 DEL key(如果 RESTORE 成功)
4. DELETE 完成后,槽位信息推送到集群

迁移期间,对迁移中的 key 的访问:

请求位置 key 状态 响应
源节点 key 还在 正常返回
源节点 key 已迁走 -ASK 重定向到目标节点
目标节点 key 已迁入 需要 ASKING 前置 → 正常返回
目标节点 key 还没来 -MOVED 到源节点

6.3 缩容流程

bash 复制代码
# 1. 迁移要下线节点的所有槽到其他节点
redis-cli --cluster reshard 192.168.1.101:6379
# Source node: 指定要下线节点的 node-id
# Destination node: 指定接收节点
# How many slots: 这个节点的槽总数

# 2. 确认槽已清空后,下线节点
redis-cli --cluster del-node 192.168.1.101:6379 <要下线节点的node-id>

# 3. 如果节点有从库,先删从库再删主库

七、生产环境部署与配置

7.1 最小集群拓扑

生产环境的最小推荐配置:3 主 3 从(共 6 个 Redis 实例)

复制代码
┌────────────────────────────────────────────────┐
│                  Redis Cluster                  │
├──────────┬──────────┬──────────┬───────────────┤
│ Master-1 │ Master-2 │ Master-3 │               │
│ :6379    │ :6380    │ :6381    │               │
│ 槽0~5460 │ 槽5461~  │ 槽10923~ │               │
│          │ 10922    │ 16383    │               │
├──────────┼──────────┼──────────┤               │
│ Slave-1  │ Slave-2  │ Slave-3  │               │
│ :6382    │ :6383    │ :6384    │               │
└──────────┴──────────┴──────────┴───────────────┘

为什么是 3 主 3 从? 如果只有 2 个主节点,一个主节点挂了,剩余的节点无法形成过半数(需要 ≥ N/2+1 = 2 票,但只有一个能投票)。3 个主节点才能容忍 1 个主节点故障。

7.2 完整 Cluster 配置

bash 复制代码
# redis.conf ------ 每个集群节点通用配置

# ============ 基本配置 ============
port 6379
bind 0.0.0.0
cluster-enabled yes                       # 开启集群模式
cluster-config-file nodes-6379.conf       # 集群状态持久化文件(自动生成)
cluster-node-timeout 15000                # 节点超时(毫秒),默认 15 秒

# ============ 持久化(强烈建议开启)============
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec

# ============ 主从配置 ============
# 不需要 replicaof ------ 集群模式下由集群自动管理主从关系
# 从库必须设置,否则主库故障后从库无法升主
masterauth "your_password"

# ============ 集群相关优化 ============
# 控制是否在从库故障时自动迁移副本
cluster-migration-barrier 1
# 含义:如果主库拥有 ≥ 1 个从库,当某个主库失去所有从库时,
# 从从库多余的主库上迁移一个从库过去(保持每个主库都有从库)

# 集群消息是否需要完整数据校验
cluster-require-full-coverage yes
# yes:任一槽位不可用时集群停止服务(推荐,数据安全)
# no:部分槽不可用仍继续服务(高可用优先)

# 副本有效性因子(replica-validity-factor)
cluster-replica-validity-factor 10
# 从库与主库断连超过 cluster-node-timeout × 10 后
# 失去故障转移资格,避免数据落后的从库升主

7.3 创建集群

bash 复制代码
# Redis 5.0+ 推荐方式
redis-cli --cluster create \
  192.168.1.101:6379 \
  192.168.1.102:6379 \
  192.168.1.103:6379 \
  192.168.1.101:6380 \
  192.168.1.102:6380 \
  192.168.1.103:6380 \
  --cluster-replicas 1
#  ↑ 每个主库配 1 个从库(自动配对)

# 验证集群状态
redis-cli --cluster check 192.168.1.101:6379

# 查看槽分配
redis-cli -c -h 192.168.1.101 -p 6379 CLUSTER SLOTS
redis-cli -c -h 192.168.1.101 -p 6379 CLUSTER NODES

7.4 集群状态检查常用命令

bash 复制代码
# 查看集群节点信息
CLUSTER NODES
# 输出解读:
# <node-id> <ip:port@cport> <flags> <master-id> <ping-sent> <pong-recv> <epoch> <link-state> <slot-range>
# flags: myself, master, slave, fail?, fail, handshake, noaddr, noflags

# 查看槽位分配
CLUSTER SLOTS

# 查看本节点的集群信息
CLUSTER INFO
# cluster_state:ok         ← 集群状态(必须为 ok)
# cluster_slots_assigned:16384
# cluster_slots_ok:16384   ← 可用槽数
# cluster_size:3           ← 主节点数

# 查看 key 属于哪个槽
CLUSTER KEYSLOT mykey     # 返回槽号

# 查看槽中有多少个 key
CLUSTER COUNTKEYSINSLOT 1000

# 修复集群(槽位不一致时)
redis-cli --cluster fix 192.168.1.101:6379

八、常见问题与避坑指南

8.1 坑 1:多 key 操作报错

bash 复制代码
# ❌ 错误------多个 key 不在同一个槽
MGET key1 key2 key3
# (error) CROSSSLOT Keys in request don't hash to the same slot

# ✅ 方案一:使用 Hash Tag
MGET {user:1001}:name {user:1001}:age {user:1001}:email

# ✅ 方案二:业务层拆分,逐个获取后聚合

8.2 坑 2:事务、Lua 脚本限制

Cluster 模式下,MULTI/EXEC 事务和 Lua 脚本中的所有 key 必须在同一个槽。

bash 复制代码
# ❌ 错误
EVAL "return redis.call('GET', KEYS[1]) .. redis.call('GET', KEYS[2])" 2 key1 key2

# ✅ 使用 Hash Tag
EVAL "..." 2 {group}:key1 {group}:key2

8.3 坑 3:槽位全部不可用时集群停服

bash 复制代码
# 排查
CLUSTER INFO | grep cluster_state
# cluster_state:fail  ← 有槽位不可用

# 常见原因
# - 某个主节点挂了且没有从库可以接替
# - 正在进行槽迁移但过程出错
# - 网络分区导致节点孤立

# 临时恢复(如果确认数据可接受)
cluster-require-full-coverage no  # 设置为 no 后重启

8.4 坑 4:客户端不感知 MOVED 重定向

bash 复制代码
# 如果客户端没有开启集群模式(如直接连单节点用 redis-cli 不加 -c)
SET key "value"
# (error) MOVED 12345 192.168.1.102:6379

# 解决:
# 1. redis-cli 加 -c 参数
redis-cli -c -h 192.168.1.101 -p 6379

# 2. 代码中使用集群客户端
# Jedis: JedisCluster
# Lettuce: RedisClusterClient
# Redisson: RedissonClient (with ClusterServersConfig)

8.5 坑 5:数据倾斜

bash 复制代码
# 查看各节点的槽数和 key 数量
redis-cli --cluster check 192.168.1.101:6379 | grep "slots"

# 如果发现某节点 key 数量远大于其他节点:
# 1. 检查是否有大量使用了同一个 Hash Tag 的 key
# 2. 手动迁移部分槽(reshard)
# 3. 考虑使用 Redis 7.0 的 rebalance 功能
redis-cli --cluster rebalance 192.168.1.101:6379

九、总结

本文核心知识体系:
#mermaid-svg-kW8z0AqyUxuz65Md{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kW8z0AqyUxuz65Md .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kW8z0AqyUxuz65Md .error-icon{fill:#552222;}#mermaid-svg-kW8z0AqyUxuz65Md .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kW8z0AqyUxuz65Md .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kW8z0AqyUxuz65Md .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kW8z0AqyUxuz65Md .marker.cross{stroke:#333333;}#mermaid-svg-kW8z0AqyUxuz65Md svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kW8z0AqyUxuz65Md p{margin:0;}#mermaid-svg-kW8z0AqyUxuz65Md .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kW8z0AqyUxuz65Md .cluster-label text{fill:#333;}#mermaid-svg-kW8z0AqyUxuz65Md .cluster-label span{color:#333;}#mermaid-svg-kW8z0AqyUxuz65Md .cluster-label span p{background-color:transparent;}#mermaid-svg-kW8z0AqyUxuz65Md .label text,#mermaid-svg-kW8z0AqyUxuz65Md span{fill:#333;color:#333;}#mermaid-svg-kW8z0AqyUxuz65Md .node rect,#mermaid-svg-kW8z0AqyUxuz65Md .node circle,#mermaid-svg-kW8z0AqyUxuz65Md .node ellipse,#mermaid-svg-kW8z0AqyUxuz65Md .node polygon,#mermaid-svg-kW8z0AqyUxuz65Md .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kW8z0AqyUxuz65Md .rough-node .label text,#mermaid-svg-kW8z0AqyUxuz65Md .node .label text,#mermaid-svg-kW8z0AqyUxuz65Md .image-shape .label,#mermaid-svg-kW8z0AqyUxuz65Md .icon-shape .label{text-anchor:middle;}#mermaid-svg-kW8z0AqyUxuz65Md .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kW8z0AqyUxuz65Md .rough-node .label,#mermaid-svg-kW8z0AqyUxuz65Md .node .label,#mermaid-svg-kW8z0AqyUxuz65Md .image-shape .label,#mermaid-svg-kW8z0AqyUxuz65Md .icon-shape .label{text-align:center;}#mermaid-svg-kW8z0AqyUxuz65Md .node.clickable{cursor:pointer;}#mermaid-svg-kW8z0AqyUxuz65Md .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kW8z0AqyUxuz65Md .arrowheadPath{fill:#333333;}#mermaid-svg-kW8z0AqyUxuz65Md .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kW8z0AqyUxuz65Md .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kW8z0AqyUxuz65Md .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kW8z0AqyUxuz65Md .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kW8z0AqyUxuz65Md .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kW8z0AqyUxuz65Md .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kW8z0AqyUxuz65Md .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kW8z0AqyUxuz65Md .cluster text{fill:#333;}#mermaid-svg-kW8z0AqyUxuz65Md .cluster span{color:#333;}#mermaid-svg-kW8z0AqyUxuz65Md div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kW8z0AqyUxuz65Md .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kW8z0AqyUxuz65Md rect.text{fill:none;stroke-width:0;}#mermaid-svg-kW8z0AqyUxuz65Md .icon-shape,#mermaid-svg-kW8z0AqyUxuz65Md .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kW8z0AqyUxuz65Md .icon-shape p,#mermaid-svg-kW8z0AqyUxuz65Md .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kW8z0AqyUxuz65Md .icon-shape .label rect,#mermaid-svg-kW8z0AqyUxuz65Md .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kW8z0AqyUxuz65Md .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kW8z0AqyUxuz65Md .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kW8z0AqyUxuz65Md :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Redis Cluster
数据分片
节点通信
高可用
运维
16384 哈希槽
CRC16(key) % 16384
Hash Tag 分组
Gossip 协议
PING/PONG/FAIL/MEET
最终一致性
PFAIL → FAIL
过半投票选主
自动故障转移
add-node / reshard / del-node
MOVED vs ASK 重定向
3 主 3 从最小集群

三个必须理清的概念

概念 关键词 本质
MOVED 永久重定向 槽不属于我,更新本地缓存
ASK 临时重定向 槽在迁移中,不更新缓存
Gossip 最终一致性 随机交换 → 逐步收敛

如有疑问或指正,欢迎在评论区交流。

相关推荐
喵了几个咪1 小时前
技术复盘:基于 GoWind Admin 实现 Kratos 框架单体轻量化落地
前端·架构
我是一颗柠檬1 小时前
【Redis】Redis分布式锁Day13(2026年)
java·redis·分布式·缓存
故渊at9 小时前
系列三:组件化与模块化进阶 | 第11篇 组件化项目规范与问题根治:依赖、资源、Manifest 与混淆的全链路管控
android·架构·mvvm·模块化·组件化
goodluckyaa12 小时前
NVIDIAGPU 架构中的不变常量(宏观 → 微观)
架构·gpu算力
wenzhangli712 小时前
AI-IDE 关键技术解析:从自然语言到企业级智能开发平台的架构演进
ide·人工智能·架构
m0_7471245313 小时前
ARM架构基础知识扫盲
arm开发·架构
pe7er14 小时前
软件设计不要“既要又要”
前端·后端·架构
X54先生(人文科技)14 小时前
《元创力》纪实录·卷宗2.1P上去的安全带:当“表演性合规”成为文明的遮羞布
人工智能·架构·开源·ai写作·开源协议
IPHWT 零软网络14 小时前
信创场景下大容量语音网关的架构设计与实践——以 MX120G-A 为例
架构·信创·国产化·语音网关