Redis Cluster的分布式架构设计
Redis Cluster是Redis官方提供的分布式方案,通过数据分片实现水平扩展,通过主从复制实现高可用。单机Redis受限于单核CPU和单机内存,在数据量超过内存容量或QPS超过单节点处理能力时需要水平扩展。Redis Cluster将数据分布在多个节点上,每个节点负责一部分数据,客户端可以直接路由请求到正确的节点,无需代理层。
Redis Cluster采用哈希槽(Hash Slot)分片策略,将所有键空间划分为16384个槽。每个键通过CRC16计算哈希值后对16384取模,确定所属槽位。集群中的每个主节点负责一部分槽位。三主节点集群中,节点A负责0-5460,节点B负责5461-10922,节点C负责10923-16383。当节点数量变化时,槽位需要重新分配,但Redis Cluster支持在线迁移槽位而不中断服务。
集群搭建与初始化配置
使用Docker Compose搭建六节点集群(三主三从):
# docker-compose.yml
services:
redis-node-1:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6379:6379"]
networks: [redis-cluster]
redis-node-2:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6380:6379"]
networks: [redis-cluster]
redis-node-3:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6381:6379"]
networks: [redis-cluster]
redis-node-4:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6382:6379"]
networks: [redis-cluster]
redis-node-5:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6383:6379"]
networks: [redis-cluster]
redis-node-6:
image: redis:7.4-alpine
command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
ports: ["6384:6379"]
networks: [redis-cluster]
networks:
redis-cluster:
driver: bridge
关键配置参数:cluster-enabled yes启用集群模式;cluster-config-file指定集群元数据持久化文件;cluster-node-timeout设置节点超时判定时间(毫秒),超过此时间未收到节点心跳则判定为故障。
启动容器后执行集群创建命令:
# 创建集群,3主3从,自动分配槽位
redis-cli --cluster create \
127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 \
127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 \
--cluster-replicas 1
# 检查集群状态
redis-cli -p 6379 cluster info
redis-cli -p 6379 cluster nodes
# 查看槽位分配
redis-cli -p 6379 cluster slots
--cluster-replicas 1表示每个主节点配一个从节点。集群创建后自动分配槽位:节点1分配0-5460,节点2分配5461-10922,节点3分配10923-16383。节点4-6分别作为节点1-3的从节点,承担复制和故障转移职责。
槽位迁移与集群扩缩容
集群扩容时需要将新节点加入集群并迁移部分槽位。以下示例将新节点node-7加入集群并从现有节点迁移部分槽位:
# 1. 添加新主节点
redis-cli --cluster add-node 127.0.0.1:6385 127.0.0.1:6379
# 2. 添加从节点(指定主节点ID)
redis-cli --cluster add-node 127.0.0.1:6386 127.0.0.1:6379 \
--cluster-slave --cluster-master-id <node-7的ID>
# 3. 重新分配槽位
redis-cli --cluster reshard 127.0.0.1:6379
# 交互式输入:
# How many slots? 4096
# What is the receiving node ID? <node-7的ID>
# Source node IDs? all
# 4. 检查迁移后的槽位分布
redis-cli -p 6379 cluster slots
槽位迁移过程中,涉及的键通过MIGRATE命令在节点间原子性转移。迁移期间,对应的槽位处于migrating状态(源节点)和importing状态(目标节点)。客户端访问迁移中的槽位时,如果键在源节点存在则正常返回;如果不存在则返回ASK重定向到目标节点。应用层无需感知迁移过程,redis客户端库自动处理ASK和MOVED重定向。
# 缩容:移除节点
# 1. 将待移除节点的槽位迁移到其他节点
redis-cli --cluster reshard 127.0.0.1:6379 \
--cluster-from <node-7的ID> \
--cluster-to <node-1的ID> \
--cluster-slots 4096 \
--cluster-yes
# 2. 移除节点
redis-cli --cluster del-node 127.0.0.1:6379 <node-7的ID>
故障检测与自动故障转移机制
Redis Cluster通过Gossip协议实现节点间的故障检测。每个节点定期(每秒)向其他节点发送PING消息,携带自己已知的集群拓扑信息。接收方回复PONG消息。如果节点A在cluster-node-timeout时间内未收到节点B的PONG,则将节点B标记为PFAIL(主观下线)。当集群中超过半数的主节点都报告节点B为PFAIL,节点B被标记为FAIL(客观下线)。
节点B的从节点检测到主节点FAIL后,发起故障转移流程:
# 模拟主节点故障
docker stop redis-node-1
# 观察从节点接管
redis-cli -p 6382 cluster nodes
# node-4(原node-1的从节点)会自动升级为主节点
# 接管node-1负责的0-5460槽位
故障转移的详细过程:从节点等待一定时间(cluster-node-timeout * 2加上随机延迟),避免多个从节点同时发起选举。等待时间结束后,从节点向集群发送FAILOVER_AUTH_REQUEST消息请求投票。其他主节点收到请求后,如果该从节点的数据复制偏移量最大且主节点已确认为FAIL状态,则回复FAILOVER_AUTH_ACK。从节点获得超过半数主节点投票后,升级为新主节点,接管原主节点的槽位,并通过Gossip广播集群拓扑变更。
# 手动故障转移(计划性维护)
# 在从节点上执行
redis-cli -p 6382 cluster failover
# 带选项的手动故障转移
redis-cli -p 6382 cluster failover TAKEOVER # 强制接管
redis-cli -p 6382 cluster failover FORCE # 跳过主节点确认
客户端连接与路由重定向
Redis Cluster客户端需要实现槽位路由逻辑。以Go语言的go-redis库为例:
package main
import (
"context"
"github.com/redis/go-redis/v9"
)
func main() {
client := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"127.0.0.1:6379",
"127.0.0.1:6380",
"127.0.0.1:6381",
},
PoolSize: 100,
MinIdleConns: 10,
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
RouteRandomly: true,
ReadOnly: true,
MaxRetries: 3,
})
defer client.Close()
ctx := context.Background()
// 自动路由:根据CRC16计算键的槽位
client.Set(ctx, "user:1001", "data", 0)
// Hash Tag:强制多个键分配到同一槽位
client.Set(ctx, "{user:1001}.profile", "data", 0)
client.Set(ctx, "{user:1001}.settings", "data", 0)
// 批量操作(同槽位)
values, _ := client.MGet(ctx, "{user:1001}.profile", "{user:1001}.settings").Result()
// 跨槽位批量操作使用Pipeline
pipe := client.Pipeline()
pipe.Set(ctx, "key1", "val1", 0)
pipe.Set(ctx, "key2", "val2", 0)
pipe.Set(ctx, "key3", "val3", 0)
cmders, _ := pipe.Exec(ctx)
}
Hash Tag是Redis Cluster中处理多键操作的关键机制。键中{}包裹的子串参与CRC16计算,{user:1001}.profile和{user:1001}.settings被分配到同一槽位,支持MGET、MSET、MULTI/EXEC等需要多键原子的操作。设计Key结构时提前规划Hash Tag,避免运行时跨槽位操作失败。生产环境中定期监控cluster_state(必须为ok)、cluster_slots_assigned(必须为16384)和各节点内存使用情况,通过redis-cli --cluster check做集群完整性巡检。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/rediscluster-ji-qun-da-jian-shi-zhan-cao-wei-fen-pei-yuan/