一文搞懂 Redis 集群 (Redis Cluster)
一文搞懂 Redis 集群 (Redis Cluster)
Redis,作为当今最流行的内存数据结构存储系统之一,以其高性能、丰富的数据结构和原子操作等特性,在缓存、消息队列、排行榜、分布式锁等众多场景中得到了广泛应用。然而,随着业务量的增长和数据规模的扩大,单机 Redis 实例往往会遇到瓶颈:
- 内存容量限制: 单个 Redis 实例的内存容量受限于服务器物理内存,当数据量超过单机内存上限时,无法继续存储。
- 并发处理能力限制: Redis 的主要工作线程是单线程的(虽然 I/O 和部分后台任务是多线程的),在高并发场景下,单个实例的 QPS 存在上限。
- 可用性问题: 单机 Redis 存在单点故障风险,一旦该实例宕机,整个服务将不可用。
为了解决这些问题,Redis 官方在 3.0 版本推出了 Redis Cluster,这是一个原生的、分布式的、高可用的解决方案。本文将深入探讨 Redis Cluster 的方方面面,助你彻底搞懂它。
一、 为什么需要 Redis Cluster?—— 单机 Redis 的痛点
在深入了解 Redis Cluster 之前,我们先回顾一下单机 Redis 面临的主要挑战,这也正是 Redis Cluster 要解决的核心问题:
- 数据量剧增,内存不够用: 业务发展迅速,需要缓存或存储的数据越来越多,单台服务器的内存往往捉襟见肘。即使使用 RDB 或 AOF 持久化,数据最终还是要加载到内存中才能提供服务。
- 并发量提升,CPU 成瓶颈: Redis 的单线程模型虽然避免了多线程上下文切换的开销,但在 CPU 密集型操作或极高的并发请求下,单核 CPU 会成为性能瓶颈。
- 服务高可用,单点故障无法容忍: 对于核心业务,系统需要 7x24 小时稳定运行。单机 Redis 一旦发生故障(硬件损坏、进程崩溃、网络中断等),将导致相关服务中断,影响用户体验甚至造成经济损失。
虽然在 Redis Cluster 出现之前,社区也有一些解决方案,如客户端分片、代理层分片(如 Twemproxy, Codis)以及基于主从复制和 Sentinel 的高可用方案,但它们各有优劣:
- 客户端分片: 逻辑分散在各个客户端,难以统一管理,升级维护复杂。
- 代理层分片: 引入了额外的代理层,增加了系统复杂度和潜在的性能瓶颈,代理本身也可能成为单点。
- Sentinel + 主从: 主要解决高可用问题,但无法解决数据分片和内存/CPU 扩展问题,所有数据仍然存储在一个 Master 节点上。
Redis Cluster 的出现,旨在提供一个 官方的、内置的、无需中心节点或代理的 集群解决方案,同时解决 数据分片(水平扩展) 和 高可用(故障转移) 两大核心问题。
二、 Redis Cluster 核心概念解析
理解 Redis Cluster 的工作原理,首先要掌握其几个核心概念:
1. 数据分片 (Data Sharding) 与哈希槽 (Slot)
为了将数据分散到不同的节点上,Redis Cluster 引入了 哈希槽 (Hash Slot) 的概念。整个集群预设了 16384 (0 ~ 16383) 个哈希槽。
- Key 与 Slot 的映射: 当客户端需要操作某个 Key 时,Redis Cluster 会使用 CRC16 算法 计算该 Key 的校验和,然后对 16384 取模,得到一个 0 到 16383 之间的整数,这个整数就是该 Key 所属的哈希槽。
HASH_SLOT = CRC16(key) mod 16384
-
Slot 与节点的分配: 集群中的每个 主节点 (Master Node) 负责处理一部分哈希槽。例如,一个包含 3 个主节点的集群,可能会这样分配:
- 节点 A 负责槽 0 ~ 5460
- 节点 B 负责槽 5461 ~ 10922
- 节点 C 负责槽 10923 ~ 16383
这种 槽位->节点 的映射关系存储在每个节点上,并且通过 Gossip 协议在集群内部同步。客户端连接集群中的任意节点,都能获取到完整的槽位映射信息。
-
哈希标签 (Hash Tags): Redis Cluster 规定,如果 Key 中包含
{}
并且{}
中有内容,那么计算哈希槽时只会使用{}
内的部分。例如,{user:1000}:name
和{user:1000}:age
这两个 Key,计算 Slot 时都只会使用user:1000
这部分,因此它们会被分配到同一个哈希槽。这对于需要进行多 Key 操作(如 MGET, MSET, 事务, Lua 脚本)的场景非常重要,因为 Redis Cluster 的多 Key 操作要求所有 Key 必须位于同一个槽中。
2. 节点 (Node) 与集群总线 (Cluster Bus)
- 节点角色: Redis Cluster 中的节点分为 主节点 (Master) 和 从节点 (Slave)。主节点负责处理读写请求和维护自己负责的哈希槽数据;从节点主要用于复制对应主节点的数据,并在主节点宕机时参与选举,尝试成为新的主节点,实现故障转移。
- P2P 通信: Redis Cluster 是一个 去中心化 的架构,节点之间通过 Gossip 协议 进行 P2P 通信,交换状态信息(节点存活状态、槽位分配信息、配置变更等)。
- 集群总线 (Cluster Bus): 每个 Redis Cluster 节点除了监听用于客户端连接的端口(如 6379)外,还会额外监听一个 集群总线端口,端口号通常是客户端端口号 加 10000(如 16379)。节点之间通过这个总线端口使用二进制协议进行通信,交换 Gossip 消息、心跳包、故障检测信息等。这个总线是集群能够自我管理和维护的关键。
3. 主从复制 (Master-Slave Replication)
Redis Cluster 内部的主从复制机制与单机模式下的主从复制类似。每个主节点可以拥有一个或多个从节点。
- 数据冗余: 从节点复制主节点的数据,提供数据备份。
- 故障转移基础: 当主节点发生故障时,其从节点可以被选举为新的主节点,接管原来的哈希槽,保证服务的连续性。
- 读扩展(有限): 默认情况下,Redis Cluster 会将读请求也路由到负责该槽的主节点。但客户端可以通过发送
READONLY
命令连接到从节点,进行读操作,以分担主节点的读压力。但需要注意数据一致性问题(从节点数据可能略有延迟)。
4. Gossip 协议
Gossip 协议是一种 P2P 的、最终一致性的信息传播协议。在 Redis Cluster 中,它用于:
- 节点发现: 新节点加入集群时,通过 Gossip 协议与其他节点建立联系。
- 状态传播: 节点之间互相交换各自知道的集群状态信息,如哪些节点存活、哪些节点疑似下线、哈希槽的分配情况等。通过不断交换信息,最终整个集群的所有节点都会拥有相似的集群视图(可能存在短暂的不一致)。
- 故障检测: 节点之间会定期发送 PING 包,如果某个节点在一定时间内没有收到另一个节点的 PONG 响应,就会将其标记为 疑似下线 (PFAIL - Possible Fail)。当集群中 超过半数 的主节点都将某个节点标记为 PFAIL 时,该节点会被标记为 客观下线 (FAIL),并触发后续的故障转移流程。
Gossip 协议的优点是去中心化、容错性好,即使部分节点通信暂时中断,信息最终也能通过其他节点传播。缺点是信息传播有延迟,集群状态的收敛需要一定时间。
5. 重定向 (Redirection - MOVED 与 ASK)
由于客户端可能连接到集群中的任意节点,而这个节点不一定是负责处理目标 Key 所在哈希槽的节点,因此 Redis Cluster 设计了重定向机制:
-
MOVED 重定向: 当客户端向一个节点发送命令,但该命令所操作的 Key 对应的哈希槽 并不由该节点负责 时,该节点会返回一个
MOVED
错误,并携带目标槽位以及负责该槽位的正确节点的 IP 和端口。
GET mykey
-MOVED 12345 192.168.1.100:6380
智能客户端 (Cluster-aware Client) 在收到MOVED
错误后,会 更新自己内部缓存的槽位映射关系,然后自动将命令 重新发送 到正确的节点。后续对该槽的操作会直接发送到新节点。这是一个 永久性 的重定向(直到下次集群拓扑变更)。 -
ASK 重定向:
ASK
重定向发生在 哈希槽迁移 (Resharding) 的过程中。当一个槽正在从节点 A 迁移到节点 B 时:- 如果客户端请求的 Key 对应的槽仍在节点 A,但在迁移计划中,节点 A 会检查这个 Key 是否实际存在于本地。
- 如果 Key 存在于节点 A,则正常处理。
- 如果 Key 不存在于节点 A(可能已迁移到 B,或者本来就不存在),节点 A 会返回一个
ASK
错误,指向正在接收该槽的节点 B。
GET migratingkey
-ASK 12345 192.168.1.101:6381
- 客户端收到
ASK
错误后,不会更新本地的槽位映射。它需要先向目标节点 B 发送一个ASKING
命令,表明下一个命令是由于ASK
重定向而来的。然后,再将原始命令发送给节点 B。 - 节点 B 收到
ASKING
命令后,会为这个连接设置一个一次性标志,允许执行来自一个正在导入的槽的命令。执行完下一个命令后,该标志自动清除。
ASK
重定向是 临时性 的,只在槽迁移期间发生,用于引导客户端在迁移未完成时找到可能已经迁移过去的数据。 - 如果客户端请求的 Key 对应的槽仍在节点 A,但在迁移计划中,节点 A 会检查这个 Key 是否实际存在于本地。
6. 故障转移 (Failover)
高可用是 Redis Cluster 的核心特性之一。当一个主节点被标记为客观下线 (FAIL) 后,其下属的从节点会发起故障转移流程:
- 资格检查: 只有与故障主节点断线时间未超过阈值(
cluster-node-timeout * cluster-slave-validity-factor
)的从节点才有资格参与选举。 - 选举延迟: 为了让数据更完整(复制偏移量更大)的从节点更有机会胜出,从节点会根据自己的复制偏移量计算一个延迟时间,偏移量越大,延迟越短,越早发起选举。
- 发起选举: 延迟结束后,从节点向集群中的其他主节点发送
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST
请求投票。 - 投票: 主节点在一定时间窗口内只会给第一个请求投票的、符合资格的从节点投票。只有获得 超过半数 主节点投票的从节点才能胜选。
- 替换主节点: 胜选的从节点执行
SLAVEOF no one
命令,提升自己为主节点,接管原故障主节点负责的所有哈希槽,并向集群广播自己的新状态。
整个故障转移过程是自动进行的,无需人工干预,保证了集群的可用性。
三、 Redis Cluster 架构概览
Redis Cluster 采用 无中心节点的对等网络 (Peer-to-Peer) 架构。
- 节点互联: 所有节点(无论主从)都通过集群总线互相连接,形成一个 网状结构 (Mesh)。
- 数据分布: 数据根据 Key 的哈希槽值分布在不同的主节点上。
- 高可用保障: 每个主节点至少有一个从节点作为备份,并通过内部的故障检测和自动故障转移机制保障服务连续性。
- 客户端交互: 客户端需要使用支持 Redis Cluster 协议的 智能客户端。客户端内部维护一份 槽位 -> 节点 的映射缓存。当发送命令时,客户端计算 Key 的槽位,直接将命令发送到对应的节点。如果收到
MOVED
或ASK
重定向,客户端能自动处理并更新映射缓存。
这种架构避免了单点瓶颈和代理层的复杂性,具有良好的可扩展性和容错性。
四、 Redis Cluster 如何工作?(客户端视角)
- 连接与初始化: 客户端启动时,需要配置集群中 至少一个 节点的地址。客户端会连接到这个(或这些)节点,并通过
CLUSTER NODES
或CLUSTER SLOTS
命令获取 完整的集群拓扑信息和槽位映射关系,缓存在本地内存中。 - 命令执行:
- 客户端计算要操作的 Key 所属的哈希槽。
- 根据本地缓存的槽位映射,找到负责该槽的主节点。
- 将命令直接发送给该主节点。
- 接收并处理响应。
- 处理重定向:
- 如果收到
MOVED
响应,说明本地缓存的槽位映射已过期。客户端会更新缓存,并将命令 重新发送 到MOVED
指向的新节点。 - 如果收到
ASK
响应,说明 Key 所在的槽正在迁移。客户端 不会更新 缓存,而是先向ASK
指向的目标节点发送ASKING
命令,然后再将原始命令发送给该目标节点。
- 如果收到
- 集群状态维护: 智能客户端会持续关注集群状态变化。例如,当某个节点不可达或收到
MOVED
时,客户端可能会触发重新获取集群拓扑信息的操作,以保持本地映射的准确性。
可见,智能客户端 在 Redis Cluster 中扮演了非常重要的角色,它屏蔽了集群内部的复杂性(如路由、重定向),让开发者可以像使用单机 Redis 一样操作集群。常用的 Redis 客户端库(如 Jedis, Lettuce, redis-py 等)都提供了对 Redis Cluster 的支持。
五、 Redis Cluster 的优点
- 水平扩展能力 (Scalability): 可以通过增加主节点来线性扩展集群的内存容量和处理能力。数据自动分片到新节点。
- 高可用性 (High Availability): 基于主从复制和自动故障转移机制,当主节点宕机时,能自动选举从节点接替,服务中断时间很短。
- 分布式存储 (Data Distribution): 数据自动分散到多个节点,降低了单节点的负载和内存压力。
- 去中心化架构 (Decentralized): 无需中心节点或代理服务器,节点之间对等通信,部署和维护相对简单,避免了中心节点的瓶颈和单点故障。
- 官方支持 (Official Support): 作为 Redis 官方提供的集群方案,有持续的维护和更新保障。
六、 Redis Cluster 的缺点与限制
- 多键操作受限 (Multi-key Operations Limitation): 涉及多个 Key 的原子操作(如 MSET, MGET, DEL, 事务, Lua 脚本)要求所有 Key 必须位于 同一个哈希槽 中。需要通过 哈希标签 (Hash Tags)
{...}
来确保相关 Key 落在同一槽位,这在一定程度上增加了开发复杂性。 - 事务支持有限 (Limited Transaction Support): 事务只能在单个节点内执行,无法实现跨节点的分布式事务。
- 数据迁移开销 (Resharding Overhead): 在增加或移除节点,或者重新平衡槽位分配(Resharding)时,需要迁移大量数据,这会消耗网络带宽和节点 CPU 资源,可能对在线服务产生一定影响。迁移过程中
ASK
重定向也会轻微增加延迟。 - Gossip 协议开销: 节点间频繁的 Gossip 通信会消耗一定的网络带宽和 CPU 资源,尤其在集群规模较大时。
- 客户端兼容性要求 (Client Compatibility): 必须使用支持 Redis Cluster 协议的客户端。老旧的或不支持集群协议的客户端无法直接与 Redis Cluster 交互。
- 至少需要 3 个主节点才能保证高可用: 为了在发生网络分区或节点故障时能够可靠地进行故障检测和选举(需要超过半数主节点参与),官方推荐生产环境至少部署 3 个主节点。加上从节点,最小的健壮集群通常需要 6 个节点(3 主 3 从)。
七、 部署与运维考量
- 配置: 启用 Redis Cluster 需要在每个节点的
redis.conf
文件中设置cluster-enabled yes
。还需要配置cluster-config-file
(存储集群状态的文件),cluster-node-timeout
(节点超时时间) 等参数。 - 集群创建: 可以使用 Redis 5.0 及以后版本自带的
redis-cli --cluster create host1:port1 ... hostN:portN --cluster-replicas <N>
命令来快速创建和配置集群(包括槽位分配和主从关系设置)。在早期版本中,通常使用redis-trib.rb
脚本(Ruby 编写)。 - 集群管理: 可以通过
redis-cli -c
(c 表示 cluster mode) 连接到集群,并使用CLUSTER INFO
,CLUSTER NODES
,CLUSTER SLOTS
等命令查看集群状态。添加/删除节点、重新分片等操作也可以通过redis-cli --cluster
相关子命令完成。 - 监控: 需要监控集群中每个节点的健康状况、内存使用、网络流量、命令延迟,以及集群整体的槽位分布、故障转移事件等。
八、 Redis Cluster vs. Sentinel
经常有人会将 Redis Cluster 与 Redis Sentinel 进行比较。它们解决的问题有所不同:
- Redis Sentinel: 主要目标是 高可用。它监控一组 主从复制 的 Redis 实例,在主节点宕机时自动进行故障转移,选举新的主节点。但 Sentinel 本身不负责数据分片,所有数据仍然存储在一个 Redis 主实例上。它解决了单点故障问题,但没有解决单机容量和性能瓶颈问题。
- Redis Cluster: 同时解决了 高可用 和 数据分片(水平扩展) 的问题。它将数据分散到多个主节点上,每个主节点可以有从节点备份,并具备自动故障转移能力。
选择依据:
- 如果你的数据量和并发量不大,单机 Redis 能够承载,但需要保证服务的高可用性,那么 Redis Sentinel + 主从复制 是一个更简单、资源消耗更少的选择。
- 如果你的数据量巨大,或者并发压力很高,单机 Redis 无法满足需求,需要进行水平扩展,同时还需要高可用保障,那么 Redis Cluster 是官方推荐的、更全面的解决方案。
九、 总结
Redis Cluster 是 Redis 官方提供的强大武器,用于构建大规模、高可用的 Redis 服务。它通过 哈希槽 机制实现了数据的 自动分片,解决了单机 Redis 的容量和性能瓶颈;通过 主从复制 和基于 Gossip 协议 的 自动故障检测与转移 机制,保障了服务的高可用性。其 去中心化 的架构设计使得集群具有良好的 可扩展性 和 容错性。
当然,Redis Cluster 也并非银弹,它带来了更高的复杂性,尤其是在多键操作和事务方面有所限制,并且对客户端有特定要求。理解其核心概念,如哈希槽、Gossip 协议、节点通信、重定向(MOVED/ASK)和故障转移流程,是有效使用和运维 Redis Cluster 的关键。
希望通过本文的详细介绍,你已经对 Redis Cluster 有了全面而深入的理解,能够根据自身的业务需求,做出明智的技术选型和应用。