一、 核心目标
自动分片 (Automatic Sharding): 将数据分布存储在集群的多个节点上。
高可用 (High Availability): 通过主从复制,在主节点故障时自动进行故障转移(failover),由从节点接替主节点工作。
线性扩展 (Linear Scalability): 通过增加节点可以近乎线性地提升集群的存储容量和处理能力。
二、 关键概念
节点 (Node):
集群由多个 Redis 节点组成。
每个节点都有一个唯一的 ID。
每个节点都存储一份集群配置信息(cluster state),包含所有节点的信息、槽映射等。
节点分为两种角色:主节点 (Master) 和 从节点 (Replica/Slave)。
槽 (Slot):
Redis 集群将所有数据划分为 16384 (0 - 16383) 个哈希槽。
数据分片的基本单位是槽,而不是节点或键。
集群通过将不同的槽分配给不同的主节点来实现数据分片。
键空间分片 (Keyspace Sharding):
客户端写入或读取一个键时,Redis 使用 CRC16 算法计算该键的哈希值。
哈希值对 16384 取模 (
CRC16(key) % 16384
),得到一个介于 0 到 16383 之间的整数,这个整数就是该键所属的槽号。客户端根据集群配置信息,知道哪个槽由哪个主节点负责,从而将命令路由到正确的节点。
主节点 (Master):
负责处理客户端对其所负责槽的读写请求。
存储其所负责槽的所有数据。
参与集群状态管理和故障检测。
从节点 (Replica / Slave):
复制其关联主节点的数据。
当主节点发生故障时,在满足一定条件下,从节点可以通过选举被提升为新的主节点(故障转移),接管原主节点负责的槽。
提供只读访问能力(可以配置
readonly
),分担主节点的读压力。
集群总线 (Cluster Bus):
每个 Redis 节点都额外监听一个端口(通常是客户端端口 + 10000,如 6379 -> 16379),用于节点间通信。
节点间使用二进制协议 (Gossip Protocol) 进行通信,包括:
传播节点信息: 告知其他节点自己的存在、角色、负责的槽、状态等。
传播集群状态: 更新集群配置纪元(epoch)和槽分配信息。
故障检测 (Failure Detection): 通过 Ping/Pong 消息检测其他节点是否可达。节点会标记疑似下线 (
PFAIL
) 和确认下线 (FAIL
) 的节点。故障转移授权: 在主节点确认下线 (
FAIL
) 后,协调从节点进行故障转移。发布/订阅消息: 用于手动故障转移 (
CLUSTER FAILOVER
) 等操作。
配置纪元 (Configuration Epoch):
一个单调递增的计数器。
用于在集群状态变更(如槽迁移、故障转移)时进行版本控制和冲突解决。
拥有更高配置纪元的节点信息会覆盖旧纪元的信息,确保集群最终达成一致。
三、 集群如何工作?
启动集群:
启动多个 Redis 节点,配置
cluster-enabled yes
。使用
redis-cli --cluster create
命令或手动执行CLUSTER MEET
命令让节点互相发现并组成集群。将 16384 个槽分配给各个主节点 (
CLUSTER ADDSLOTS
或redis-cli --cluster addslots
)。为每个主节点配置一个或多个从节点 (
CLUSTER REPLICATE <master-node-id>
或redis-cli --cluster add-node
时指定--cluster-slave
)。
客户端访问:
客户端需要是 Redis 集群感知 (Cluster-Aware) 的客户端(如 JedisCluster, redis-py-cluster, StackExchange.Redis 等)。
Smart Client:
客户端启动时获取一份集群槽位配置映射关系(可通过连接任意节点执行
CLUSTER SLOTS
或CLUSTER NODES
命令获得)。计算键对应的槽号 (
CRC16(key) % 16384
)。根据本地缓存的槽-节点映射,将命令直接发送给负责该槽的主节点。
重定向 (Redirection):
如果客户端缓存过期或槽正在迁移,节点可能返回:
MOVED <slot> <ip>:<port>: 表示该槽已永久迁移到另一个节点。客户端应更新本地映射并重试到新节点。
ASK <slot> <ip>:<port>: 表示该槽正在迁移过程中,目标节点临时负责处理该槽的请求(仅针对本次查询)。客户端应先发送
ASKING
命令到目标节点,然后再发送原命令(只针对这个键)。客户端不需要更新本地槽映射。
主从复制:
与单机 Redis 的主从复制机制相同。从节点异步复制主节点的数据。
故障检测与转移 (Failover):
故障检测:
节点间通过 Gossip 协议定期发送 Ping/Pong 消息。
如果一个节点在
cluster-node-timeout
时间内无法与另一个节点通信,它会将其标记为PFAIL
(Possible Failure)。当某个节点收集到集群中大多数主节点都认为某个节点
PFAIL
时,它会将该节点标记为FAIL
(确认下线)。
故障转移:
当主节点被标记为
FAIL
时,其下的从节点会尝试发起故障转移。从节点会等待一个延迟时间(基于其复制偏移量排名,复制偏移量越接近主节点的从节点延迟越短),然后发起选举。
从节点向集群中所有主节点广播
FAILOVER_AUTH_REQUEST
消息请求投票。主节点收到请求后,在一个纪元内只能投一票。如果主节点认为该从节点的主节点确实已
FAIL
,并且该主节点尚未投票给其他从节点,则投赞成票 (FAILOVER_AUTH_ACK
)。获得大多数主节点投票的从节点赢得选举。
赢得选举的从节点执行以下操作:
提升自己为新主节点。
接管原主节点负责的所有槽。
向集群广播
PONG
消息,宣布自己成为新主节点并更新槽分配信息。
其他节点更新本地集群配置信息(槽映射、节点角色)。
如果原主节点恢复,它会成为新主节点的从节点。
重新分片 (Resharding) - 槽迁移:
可以在线动态地增加或移除节点,并重新分配槽。
迁移过程:
确定要从源节点迁移到目标节点的槽。
在目标节点上执行
CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
,设置目标节点准备接收该槽的数据。在源节点上执行
CLUSTER SETSLOT <slot> MIGRATING <target-node-id>
,设置源节点准备迁移该槽的数据。客户端向源节点请求迁移槽中的键时:
如果键存在于源节点,正常处理。
如果键不存在于源节点(已迁移走)或请求的是 ASKING 之后的命令,返回
ASK
重定向。
使用
CLUSTER GETKEYSINSLOT <slot> <count>
获取槽中的键列表。对每个键,使用
MIGRATE <target-ip> <target-port> <key> 0 <timeout>
命令将键原子性地迁移到目标节点(键会被序列化传输,在目标节点反序列化,然后从源节点删除)。迁移完槽中的所有键后:
在集群中任意节点执行
CLUSTER SETSLOT <slot> NODE <target-node-id>
(通常在所有主节点上执行),将槽的归属权正式交给目标节点。这个命令会传播到整个集群。在源节点和目标节点上执行
CLUSTER SETSLOT <slot> NODE <target-node-id>
清除迁移/导入状态。
自动化工具: 强烈建议使用
redis-cli --cluster reshard
命令来执行复杂的重新分片操作,它封装了上述步骤并处理了大量细节。
四、 Redis 集群的优势
高可用性: 自动故障转移保证服务连续性。
大容量: 突破单机内存限制,数据分布在多个节点。
高性能: 读写请求分散到多个节点处理,提高整体吞吐量。
线性扩展: 可通过增加节点轻松扩展容量和性能。
原生支持: Redis 官方维护,兼容性好,社区支持完善。
五、 Redis 集群的限制 (重要!)
多键操作限制:
只支持对同一个槽中的多个键进行操作 (
MGET
,MSET
,DEL
,SINTER
,SUNIONSTORE
,LUA Script
等)。如果操作的键分布在不同的槽或不同的节点上,命令会失败并报错
CROSSSLOT
。需要使用hash tags
强制将相关键映射到同一个槽。
Lua 脚本限制:
Lua 脚本中访问的所有键必须在同一个槽中(除非使用
{hash tags}
确保这点)。否则脚本执行会报错。Redis 7.0 新增了
FUNCTION
命令,提供更灵活的脚本管理,但多键限制仍然存在。
事务限制:
事务中的命令涉及的键也必须都在同一个槽中。分布式事务需要应用层实现。
数据库: 集群模式下只使用 database 0 (
SELECT
命令被禁用)。Pub/Sub: 可以工作,但消息会广播到所有节点。如果需要精确的频道订阅,客户端需要连接到所有节点订阅。
批量操作:
KEYS
,SCAN
,FLUSHALL
,FLUSHDB
等命令只作用于当前节点,不会跨节点执行。需要遍历所有节点或使用redis-cli --cluster call
。复杂性: 相比单机或主从,部署、配置、监控、故障排查更复杂。
网络分区: 在发生网络分区(脑裂)时,可能牺牲小分区的可用性以保证数据一致性(需要大多数主节点可达才能进行故障转移和写操作)。
六、 何时使用 Redis 集群?
数据量超过单机内存容量。
读写吞吐量超过单机 Redis 处理能力。
需要高可用性,无法容忍单点故障。
应用能够接受 Redis 集群的限制(尤其是多键操作、Lua 脚本限制)。
七、 替代方案 (何时不用集群?)
数据量/流量不大: 单机 Redis 或主从复制+哨兵足够。
需要多键跨节点操作/Lua脚本跨键: 考虑客户端分片(Twemproxy, Codis)或垂直拆分应用逻辑,或评估是否必须。
需要强事务: Redis 集群不支持分布式事务。
需要多数据库: 集群只支持 db0。
运维复杂度敏感: 集群运维比单机/主从复杂。