news 2026/6/4 4:57:57

【Redis从入门到精通】第47篇:Redis Cluster——官方分布式集群方案全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Redis从入门到精通】第47篇:Redis Cluster——官方分布式集群方案全解析

上一篇【第46篇】Sentinel选主——Raft算法的精妙应用
下一篇【第48篇】哈希槽——Redis Cluster的数据分片机制


主从复制+Sentinel解决了高可用问题,但有一个天花板:单机容量。一个Redis实例能存多少数据,取决于机器的内存上限。当数据量超过单机容量时,就需要把数据分散到多个节点上——这就是Redis Cluster要做的事。

一、Redis Cluster的设计目标

Redis Cluster从设计之初就明确了三大目标:

┌────────────────────────────────────────────────────┐ │ Redis Cluster 三大设计目标 │ ├──────────────┬──────────────┬──────────────────────┤ │ 线性扩展 │ 高可用 │ 数据分片 │ │ Scale Out │ High Avail │ Data Sharding │ │ │ │ │ │ 水平扩展节点 │ 自动故障转移 │ 数据自动分布到 │ │ 容量随节点数 │ 无需Sentinel │ 多个节点 │ │ 线性增长 │ 内置选举机制 │ 16384个哈希槽 │ └──────────────┴──────────────┴──────────────────────┘

与主从+Sentinel方案的对比:

特性主从+SentinelRedis Cluster
数据分布所有节点存全量数据数据分散到多节点
容量上限单机内存上限随节点数线性扩展
高可用依赖Sentinel内置故障转移
客户端任意节点读写需要集群感知客户端
运维复杂度中等较高

那什么时候用主从+Sentinel,什么时候用Cluster呢?一个简单的判断标准:数据量能装在一台机器里,选主从+Sentinel;数据量一台机器装不下,选Cluster。对于99%的创业公司,一台64GB/128GB的Redis足够支撑到C轮,所以别过早优化。但当你的key数量超过千万级别,或者单机内存快撑满了,就是上Cluster的时候了。

另外,Redis Cluster有个重要的"舍"与"得":

  • 舍弃多键操作的事务性:MULTI/EXEC跨slot操作不保证原子性
  • 舍弃Lua脚本的跨节点执行:脚本中使用的key必须落在同一个slot
  • 获得线性扩展能力:加节点就能加容量,理论上可以扩展到1000个节点

这些限制是由数据分片的架构决定的——一条命令涉及的数据如果分散在多个节点上,分布式事务的代价太高了。所以设计key时要考虑"亲和性",把需要一起操作的key放到同一个slot。

二、集群节点的本质

Redis Cluster中的每个节点,本质上就是一个运行在集群模式下的Redis实例

# 启用集群模式的配置(redis.conf)cluster-enabledyescluster-config-file nodes-6379.conf# 集群配置文件(自动生成)cluster-node-timeout15000# 节点超时时间(毫秒)

关键点:

  • 集群模式下,每个实例有两个端口:客户端端口(如6379)和集群通信端口(客户端端口+10000,即16379)
  • 集群通信端口用于节点间的Gossip协议通信
  • 节点间不通过Sentinel,而是直接通信
Node A (6379/16379) Node B (6380/16380) │ │ │ 客户端连接 6379 │ 客户端连接 6380 │ │ │ ── Gossip (16379) ────→16380 ──→ │ ←── Gossip (16380) ←───16379 ←──

踩坑提示:确保防火墙开放了集群通信端口(客户端端口+10000)。很多人只开放了客户端端口,导致节点间无法通信,集群无法组建。同时也要开放客户端端口+10000+1(集群总线端口),虽然目前Redis只用+10000端口。

三、clusterNode结构体

每个集群节点在内存中都对应一个clusterNode结构体:

structclusterNode{// 基本标识mstime_tctime;/* 节点创建时间 */charname[CLUSTER_NAMELEN];/* 节点名称,40位十六进制 */intflags;/* 节点状态标志:MASTER/SLAVE/PFAIL/FAIL等 */uint64_tconfigEpoch;/* 配置纪元 */char*slot_info_str;/* 槽位信息字符串 */// 地址信息charip[NET_IP_STR_LEN];/* IP地址 */intport;/* 客户端端口 */intcport;/* 集群通信端口 */// 连接和状态link*link;/* 与该节点的连接 */mstime_tping_sent;/* 上次发送PING的时间 */mstime_tpong_received;/* 上次收到PONG的时间 */mstime_tfail_time;/* 被标记FAIL的时间 */// 槽位相关unsignedcharslots[CLUSTER_SLOTS/8];/* 槽位位图:该节点负责哪些槽 */intnumslots;/* 负责的槽位数 */intnumslaves;/* 从节点数量 */// 主从关系clusterNode*slaveof;/* 如果是从节点,指向主节点 */list*slaves;/* 如果是主节点,从节点列表 */// 故障检测list*fail_reports;/* 其他节点报告该节点故障的列表 */};// 关键标志位说明// CLUSTER_NODE_MASTER = 1 // 主节点// CLUSTER_NODE_SLAVE = 2 // 从节点// CLUSTER_NODE_PFAIL = 4 // 可能故障(主观)// CLUSTER_NODE_FAIL = 8 // 确认故障(客观)// CLUSTER_NODE_MYSELF = 16 // 当前节点自身// CLUSTER_NODE_HANDSHAKE = 32 // 正在握手(未完成)// CLUSTER_NODE_NOADDR = 64 // 地址未知

这几个标志位构成了节点状态机。一个节点的生命周期是这样的:通过MEET进入HANDSHAKE状态 → 握手完成后清除HANDSHAKE标志 → 如果心跳超时进入PFAIL → 如果超过半数主节点也认为它PFAIL,进入FAIL → 如果是从节点且有足够的从库晋升票数,可以晋升为MASTER。

slots字段是一个位图(unsigned char [CLUSTER_SLOTS/8],即2048字节),用于表示该节点负责哪些槽。检查一个槽是否属于某节点的操作就是简单查位图——O(1)时间。

## 四、clusterState结构体 每个节点还维护一个全局的 `clusterState`,记录整个集群的视角: ```c typedef struct clusterState { clusterNode *myself; /* 当前节点自身 */ uint64_t currentEpoch; /* 当前集群纪元 */ int state; /* 集群状态:CLUSTER_OK/CLUSTER_FAIL */ int size; /* 至少负责一个槽的主节点数 */ dict *nodes; /* 所有节点的字典:name→clusterNode */ dict *nodes_black_list; /* 黑名单节点 */ /* 槽位映射 */ clusterNode *slots[CLUSTER_SLOTS]; /* 槽→节点映射表(16384个元素)*/ clusterNode *migrating_slots_to[CLUSTER_SLOTS]; /* 正在迁出的槽 */ clusterNode *importing_slots_from[CLUSTER_SLOTS]; /* 正在迁入的槽 */ /* 故障转移 */ mstime_t failover_auth_time; /* 下次选举时间 */ int failover_auth_count; /* 收到的票数 */ int failover_auth_sent; /* 是否已发起选举 */ int failover_auth_rank; /* 从库排名 */ } clusterState;

五、CLUSTER MEET命令:让两个节点握手

要让两个节点加入同一个集群,需要在其中一个节点上执行CLUSTER MEET

# 在节点A上执行,让A和B握手redis-cli-p6379CLUSTER MEET192.168.1.1026380

握手过程:

Node A (6379) Node B (6380) │ │ │ ──── MEET ────────────────→ │ │ │ │ ←─── PONG ───────────────── │ │ │ │ ──── PING ────────────────→ │ │ │ │ ←─── PONG ───────────────── │ │ │ │ 握手完成!A和B互知对方存在 │

握手完成后,两个节点会通过Gossip协议逐渐感知到集群中的其他节点。

踩坑提示:不需要对所有节点对都执行CLUSTER MEET。只需要让每个节点与集群中任意一个节点握手,Gossip协议会自动传播节点信息。通常的做法是:选一个种子节点,让其他所有节点与它握手。

六、Gossip协议:集群信息的传播者

Gossip协议是Redis Cluster节点间通信的核心机制。它的思想很简单:每个节点定期把自己的信息告诉随机几个邻居,邻居再告诉它的邻居,最终所有节点都知道了全集群的信息

┌─────────────────────────────────────────────┐ │ Gossip 协议工作原理 │ │ │ │ A ──→ B 每隔一段时间, │ │ ↕ ╲ ╱ ↕ 每个节点随机选择 │ │ C ──→ D 几个邻居发送消息 │ │ ↕ ╲↕ │ │ E ──→ F 信息像病毒一样扩散 │ │ │ │ 最终:所有节点都拥有完整的集群视图 │ └─────────────────────────────────────────────┘

集群消息类型

消息类型说明
MEET加入集群请求(握手)
PING心跳检测 + 信息交换
PONG回复MEET/PING
PUBLISH集群内的发布/订阅
FAIL标记某个节点为FAIL
UPDATE通知其他节点更新槽位映射

Gossip消息的数据包结构:

┌──────────────────────────────────────┐ │ Gossip 消息头 │ ├──────────────────────────────────────┤ │ type: MEET/PING/PONG/... │ │ sender: 当前节点的信息 │ │ - name (40位ID) │ │ - epoch │ │ - flags (MASTER/SLAVE/...) │ │ - 负责的槽位 │ │ - IP和端口 │ ├──────────────────────────────────────┤ │ Gossip 消息体 │ ├──────────────────────────────────────┤ │ 随机选择的几个其他节点的摘要信息 │ │ - node1: name, IP, port, epoch │ │ - node2: name, IP, port, epoch │ │ - node3: ... │ └──────────────────────────────────────┘

每秒每个节点会向随机几个节点发送PING消息,消息中附带其他节点的摘要。收到PING后回复PONG,并更新自己对集群的认知。

Gossip的发送时机与反熵机制

Gossip的核心是反熵(Anti-Entropy)——每个节点通过定期和邻居交换信息来消除信息差。具体规则:

每隔 cluster-node-timeout/2 毫秒: ┌──────────────────────────────────┐ │ 从所有已知节点中随机选取 N/2+1 个 │ │ 优先选择: │ │ 1. 上次通信时间最久的节点 │ │ 2. PFAIL状态的节点 │ │ 3. 随机节点(包含已通信过的) │ └──────────────────────────────────┘

默认cluster-node-timeout为15000ms,所以每个节点大约每7.5秒向五六個节点发送一次PING。注意,PING消息不仅用于心跳检测,还携带了槽位分配信息和随机选取的其他节点的摘要,是一石二鸟的设计。

收到PING消息后,接收方会做三件事:

  1. 更新发送方信息:更新该节点的IP、端口、epoch、槽位分配
  2. 处理消息中附带的节点摘要:如果发现了自己不知道的节点,标记为HANDSHAKE状态并主动联系
  3. 回复PONG:把自己的信息和随机选取的邻居摘要带回去

这就是为什么只需要CLUSTER MEET连接一个种子节点,整个集群的节点就能互相感知——信息通过Gossip自动扩散。

七、CLUSTER INFO命令输出解读

127.0.0.1:6379>CLUSTER INFO cluster_state:ok# 集群状态:ok表示所有槽都有节点负责cluster_slots_assigned:16384# 已分配的槽位数cluster_slots_ok:16384# 正常的槽位数cluster_slots_pfail:0# 可能故障的槽位数cluster_slots_fail:0# 故障的槽位数cluster_known_nodes:6# 已知节点数cluster_size:3# 主节点数cluster_current_epoch:6# 当前集群纪元cluster_my_epoch:1# 当前节点的纪元cluster_stats_messages_sent:12345# 发送的消息数cluster_stats_messages_received:12340# 接收的消息数

八、CLUSTER NODES命令

127.0.0.1:6379>CLUSTER NODES a1b2c3...192.168.1.101:6379@16379 myself,master -016400000001connected0-5460 d4e5f6...192.168.1.102:6380@16380 master -016400000012connected5461-10922 g7h8i9...192.168.1.103:6381@16381 master -016400000023connected10923-16383 j0k1l2...192.168.1.104:6382@16382 slave a1b2c3...016400000031connected m3n4o5...192.168.1.105:6383@16383 slave d4e5f6...016400000042connected p6q7r8...192.168.1.106:6384@16384 slave g7h8i9...016400000053connected

输出格式解析:

<node-id> <ip:port@cport> <flags> <master-id> <ping-sent> <pong-recv> <epoch> <link-state> <slots> 字段说明: node-id: 节点ID(40位十六进制) ip:port@cport: 客户端端口@集群通信端口 flags: myself,master,slave,pfail,fail,handshake,noaddr,noflags master-id: 如果是从节点,指向主节点ID;主节点为"-" slots: 负责的槽位范围

九、集群最小部署要求:3主3从

Redis Cluster要求至少3个主节点,为什么?

  1. 故障转移需要多数投票:节点标记FAIL需要超过半数主节点同意(类似Sentinel的ODOWN)
  2. 3个主节点是最小多数派:2个主节点中1个挂了只剩1个,无法形成多数派
  3. 每个主节点至少1个从节点:主节点挂了,从节点可以晋升
最小集群拓扑(3主3从): ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Master A │ │ Master B │ │ Master C │ │ 0-5460 │ │ 5461-10922│ │ 10923-16383│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ │ Slave A' │ │ Slave B' │ │ Slave C' │ └──────────┘ └──────────┘ └──────────┘

踩坑提示:生产环境建议至少3主3从共6个节点。如果预算有限,可以使用3个节点各运行2个Redis实例(一个主一个从),但要确保主从不在同一台机器上。

十、集群模式下的客户端

redis-cli -c 模式

# 普通模式:不自动重定向redis-cli-p6379GET key# 如果key不在当前节点,返回 (error) MOVED 3999 192.168.1.102:6380# 集群模式:自动重定向redis-cli-c-p6379GET key# 自动重定向到正确的节点并返回结果

智能客户端

主流客户端库都支持集群模式,它们会缓存槽位映射关系:

// Jedis 集群模式Set<HostAndPort>nodes=newHashSet<>();nodes.add(newHostAndPort("192.168.1.101",6379));nodes.add(newHostAndPort("192.168.1.102",6380));nodes.add(newHostAndPort("192.168.1.103",6381));JedisClusterjedisCluster=newJedisCluster(nodes);jedisCluster.set("key","value");// 自动路由到正确节点

Docker/K8s中部署的注意事项

┌──────────────────────────────────────────────────┐ │ Docker/K8s 部署 Redis Cluster 注意事项 │ ├──────────────────────────────────────────────────┤ │ │ │ 1. 使用 host 网络模式 │ │ 容器内看到的IP和外部访问的IP一致 │ │ 否则MEET握手可能失败 │ │ │ │ 2. 或使用 cluster-announce-ip │ │ 告诉其他节点用哪个IP连接自己 │ │ CONFIG SET cluster-announce-ip 192.168.1.101 │ │ │ │ 3. 或使用 cluster-announce-port / bus-port │ │ 指定对外暴露的端口 │ │ cluster-announce-port 6379 │ │ cluster-announce-bus-port 16379 │ │ │ │ 4. K8s中避免用ClusterIP │ │ 使用 Headless Service + StatefulSet │ │ 每个Pod有稳定的网络标识 │ │ │ └──────────────────────────────────────────────────┘

踩坑提示:Docker的NAT网络是Redis Cluster的天敌。容器内部的IP(如172.17.0.2)和宿主机IP不同,节点间通信会失败。必须使用--net=host或配置cluster-announce-ip

一个可用的 Docker 部署流程

多机部署时,建议使用network_mode: host模式。以下是一个6节点集群的典型部署流程:

# 每台机器上启动Redis(假设6台机器,端口均为6379)redis-server--port6379--cluster-enabledyes\--cluster-config-file nodes-6379.conf\--cluster-node-timeout5000\--cluster-announce-ip<本机IP>\--cluster-announce-port6379\--cluster-announce-bus-port16379\--appendonlyyes--daemonizeyes# 在任意一台机器上通过redis-cli创建集群redis-cli--clustercreate\192.168.1.101:6379\192.168.1.102:6379\192.168.1.103:6379\192.168.1.104:6379\192.168.1.105:6379\192.168.1.106:6379\--cluster-replicas1

--cluster-replicas 1告诉工具:这6个节点自动分配成3主3从。前3个成为主节点,后3个成为对应主节点的从节点。

十一、CLUSTER RESET:节点的"格式化"按钮

最后介绍一个收拾残局的神器——CLUSTER RESET

# 软重置:保留当前纪元信息,清空集群拓扑CLUSTER RESET# 硬重置:完全清空,连纪元一起重置CLUSTER RESET HARD

使用场景:

  • 集群配置出现混乱,节点信息不一致
  • 想把一个节点从旧集群迁移到新集群
  • 节点之间的握手卡住了(比如IP地址变了)

先 RESET 再 MEET,等于重新让节点"单身"后再"相亲"。

踩坑提示CLUSTER RESET不会删除数据!如果节点上已有数据,这些数据仍然存在。需要手动执行FLUSHALLFLUSHDB清理,否则旧数据与新的槽位分配不匹配,会导致数据访问异常——你的key明明在节点上,但客户端根据新的槽位映射去了别的节点找。

总结

Redis Cluster通过Gossip协议实现节点间的自动发现和信息传播,通过CLUSTER MEET命令加入集群,通过16384个哈希槽实现数据分片。3主3从是最小部署要求,客户端需要支持集群模式才能正确路由请求。在容器化部署时,务必注意网络配置,确保节点间能正常通信。

下一篇,我们将深入Redis Cluster最核心的数据分片机制——16384个哈希槽的设计哲学。


上一篇【第46篇】Sentinel选主——Raft算法的精妙应用
下一篇【第48篇】哈希槽——Redis Cluster的数据分片机制


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 4:55:55

如何让老旧Mac重获新生:OpenCore Legacy Patcher完整使用指南

如何让老旧Mac重获新生&#xff1a;OpenCore Legacy Patcher完整使用指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为老旧Mac无法升级最新系统而烦…

作者头像 李华
网站建设 2026/6/4 4:54:56

DeBERTa-v2-xlarge实战教程:10个步骤教你微调自己的文本分类模型

DeBERTa-v2-xlarge实战教程&#xff1a;10个步骤教你微调自己的文本分类模型 【免费下载链接】deberta-v2-xlarge 项目地址: https://ai.gitcode.com/hf_mirrors/JiangSuAscend/deberta-v2-xlarge DeBERTa-v2-xlarge是一款强大的预训练语言模型&#xff0c;基于深度双向…

作者头像 李华
网站建设 2026/6/4 4:54:11

计算机毕业设计之基于Python的交通数据可视化系统

本研究设计并实现了一套基于Python的交通数据可视化系统&#xff0c;旨在通过先进的数据分析和可视化技术&#xff0c;为交通管理和规划提供直观、高效的决策支持。系统利用Python的丰富数据处理库和可视化工具&#xff0c;Django框架、Echarts、Vue、spark&#xff0c;对实时交…

作者头像 李华
网站建设 2026/6/4 4:54:11

深度学习常用开源数据集介绍【持续更新】

DIV2K 介绍:DIV2K是一个专为 图像超分辨率(SR) 任务设计的高质量数据集,广泛应用于计算机视觉领域的研究和开发。它包含800张高分辨率(HR)训练图像和100张高分辨率验证图像,每张图像都具有极高的清晰度,非常适合用于训练和评估超分辨率算法。通过DIV2K数据集,研究人员…

作者头像 李华