Redis Cluster是Redis官方提供的分布式方案,通过一致性哈希分片将数据分布到多个节点上,支持自动故障转移和线性扩展。与哨兵模式相比,Cluster模式不仅解决高可用问题,还解决了单节点内存上限和单点写入瓶颈。理解Cluster的路由机制、槽位分配和跨槽位操作限制,是Redis集群运维和开发的前提。
槽位分配与一致性哈希路由
Redis Cluster将key空间划分为16384个槽位(slot),每个节点负责一部分槽位。key到slot的映射使用CRC16哈希算法:slot = CRC16(key) mod 16384。客户端可以缓存slot与节点的映射关系(CLUSTER SLOTS命令获取),直接将请求发送到目标节点,避免代理层的额外延迟。
// Redis Cluster创建与槽位分配
// 启动6个Redis节点(3主3从),端口7000-7005
# redis.conf 配置
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
appendonly yes
# 使用redis-cli创建集群
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
# 集群创建后槽位分配:
# Master 7000: slots 0-5460 (5461个)
# Master 7001: slots 5461-10922 (5462个)
# Master 7002: slots 10923-16383 (5461个)
# Slave 7003 -> replicates 7000
# Slave 7004 -> replicates 7001
# Slave 7005 -> replicates 7002
# 查看集群信息
redis-cli -p 7000 cluster info
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_known_nodes:6
# 查看槽位分配
redis-cli -p 7000 cluster nodes
# 输出示例:
#id 7000 master - 0 1723... 1-5460
#id 7001 master - 0 1723... 5461-10922
#id 7002 master - 0 1723... 10923-16383
#id 7003 slave 7000 0 1723...
#id 7004 slave 7001 0 1723...
#id 7005 slave 7002 0 1723...
# 查看key所在槽位
redis-cli -p 7000 cluster keyslot user:1001
# (integer) 12345
MOVED重定向与客户端缓存
当客户端将请求发送到不持有目标槽位的节点时,该节点返回MOVED响应告知正确的目标节点。智能客户端(如Jedis、Lettuce、go-redis)会在首次连接时缓存slot映射表,后续请求直接发送到正确节点。集群扩容/缩容时slot迁移会产生短暂的MOVED重定向:
// Java Lettuce客户端Cluster连接配置
import io.lettuce.core.cluster.*;
import io.lettuce.core.cluster.models.partitions.*;
import io.lettuce.core.cluster.api.sync.*;
RedisClusterClient client = RedisClusterClient.create(
RedisURI.create("redis://127.0.0.1:7000")
);
// 拓扑刷新配置(自动感知集群变更)
ClusterTopologyRefreshOptions topologyRefresh = ClusterTopologyRefreshOptions
.builder()
.enablePeriodicRefresh(Duration.ofMinutes(5)) // 每5分钟刷新
.enableAllAdaptiveRefreshTriggers() // 事件触发刷新
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
.build();
ClusterClientOptions options = ClusterClientOptions
.builder()
.topologyRefreshOptions(topologyRefresh)
.maxRedirects(5) // MOVED/ASK最大重定向次数
.validateClusterNodeMembership(false)
.autoReconnect(true)
.build();
client.setOptions(options);
// NodeSelection API:跨节点批量操作
StatefulRedisClusterConnection<String, String> conn = client.connect();
RedisAdvancedClusterCommands<String, String> sync = conn.sync();
// 单key操作(自动路由到正确节点)
sync.set("user:1001", "zhangsan");
String value = sync.get("user:1001");
// 跨节点批量操作(分拆为多个节点请求)
Map<String, String> values = sync.mget("key1", "key2", "key3")
.stream()
.collect(Collectors.toMap(
kv -> kv.hasValue() ? kv.getKey() : "null",
kv -> kv.getValueOrElse("null")
));
// 使用NodeSelection在所有master节点执行命令
NodeSelection<String, String> masters = sync.masters();
Map<String, String> result = masters.dbsize();
// go-redis Cluster客户端配置
import "github.com/redis/go-redis/v9"
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"127.0.0.1:7000", "127.0.0.1:7001", "127.0.0.1:7002"},
Password: "",
PoolSize: 100, // 每节点连接池大小
MinIdleConns: 10,
MaxRedirects: 8, // MOVED/ASK最大重定向
ReadOnly: true, // 读请求可路由到从节点
RouteRandomly: true, // 读请求随机路由(负载均衡)
ClusterSlots: func(ctx context.Context) ([]redis.ClusterSlot, error) {
// 自定义slot映射逻辑(可选)
return nil, nil // 使用默认自动发现
},
})
Hash Tag与跨槽位事务
Redis Cluster中,事务(MULTI/EXEC)和Lua脚本要求所有操作的key必须在同一个槽位。多个key需要同槽位时使用Hash Tag——将key中{}内的部分作为CRC16计算的输入:
# Hash Tag规则:CRC16只计算{}内的部分
# key1{user100}.profile -> CRC16("user100") = slot X
# key1{user100}.session -> CRC16("user100") = slot X
# key1{user100}.cart -> CRC16("user100") = slot X
# 三个key在同一个slot,可以执行事务
# 没有Hash Tag的多key操作会报错
redis-cli -p 7000 mset user:1001:name zhangsan user:1002:name lisi
# (error) CROSSSLOT Keys in request don't hash to the same slot
# 使用Hash Tag
redis-cli -p 7000 mset {user:1001}:name zhangsan {user:1001}:age 28 {user:1001}:city beijing
# OK
# 事务操作(同一Hash Tag)
redis-cli -p 7000
127.0.0.1:7000> MULTI
127.0.0.1:7000> SET {order:5001}:status "paid"
127.0.0.1:7000> LPUSH {order:5001}:items "item1" "item2"
127.0.0.1:7000> HINCRBY {order:5001}:summary amount 299
127.0.0.1:7000> EXEC
# Lua脚本(所有key必须在同一slot)
redis-cli -p 7000 EVAL '
local stock = redis.call("GET", KEYS[1])
if tonumber(stock) >= tonumber(ARGV[1]) then
redis.call("DECRBY", KEYS[1], ARGV[1])
redis.call("HINCRBY", KEYS[2], "sold", ARGV[1])
return 1
else
return 0
end
' 2 {product:889}:stock {product:889}:sales 10
故障转移与数据一致性
当Master节点不可达时,Cluster触发故障转移。故障检测依赖Gossip协议——节点之间互相发送PING/PONG消息传播集群状态。判定不可达的流程:节点A向节点B发送PING,超时未收到PONG则标记B为PFAIL(疑似下线);超过半数Master标记B为PFAIL,则升级为FAIL(确定下线);B的Slave发起选举成为新Master。
# 手动故障转移
redis-cli -p 7003 cluster failover
# 集群脑裂场景处理
# network partition导致集群分裂
# 检查集群状态
redis-cli -p 7000 cluster info | grep cluster_state
# cluster_state:fail <- 集群不可用
# 确认哪部分节点持有多数槽位的主节点
redis-cli -p 7000 cluster nodes | grep master
# 修复:将孤立节点重新加入集群
redis-cli -p 7000 cluster meet 127.0.0.1 7003
# 槽位手动迁移(集群扩容场景)
# 1. 新节点加入集群
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
# 2. 重新分片(迁移slot)
redis-cli --cluster reshard 127.0.0.1:7000 \
--cluster-from 7000,7001,7002 \
--cluster-to 7006 \
--cluster-slots 4096 \
--cluster-yes
# 迁移过程中的ASK临时重定向:
# 客户端发送GET key1到源节点
# 源节点返回ASK 12345 127.0.0.1:7006
# 客户端发送ASKING命令到7006
# 7006临时接受该请求(迁移中的slot临时可访问)
数据备份恢复与运维最佳实践
# Redis Cluster数据备份
# 方式1:每个Master节点单独BGSAVE
for port in 7000 7001 7002; do
redis-cli -p $port BGSAVE
done
# 等待BGSAVE完成后复制rdb文件
sleep 10
for port in 7000 7001 7002; do
cp /var/lib/redis/$port/dump.rdb /backup/dump-$port-$(date +%Y%m%d).rdb
done
# 方式2:使用redis-cli --cluster备份(需管道)
for port in 7000 7001 7002; do
redis-cli -p $port --rdb /backup/cluster-$port-$(date +%Y%m%d).rdb
done
# 方式3:Redis-shake在线迁移(支持Cluster到Cluster)
# source.cluster.enabled = true
# target.cluster.enabled = true
NoSQL选型应用中Redis Cluster适合大容量缓存和session共享场景。与单节点Redis相比,Cluster模式牺牲了部分跨key操作能力(事务、Lua、Pipeline限制在单slot),换取了水平扩展和高可用。Redis缓存策略设计中,Hash Tag是解决跨槽位事务的唯一手段,但过度使用会导致数据倾斜——大量key集中到少数slot,违反分片均匀原则。数据库高可用架构中,Redis Cluster的自动故障转移时间(cluster-node-timeout + 选举时间)通常在10到30秒,期间受影响slot的请求会返回CLUSTERDOWN错误,需要在应用层实现重试和降级逻辑。数据迁移实战中,在线扩容操作应避开流量高峰,reshard过程中密切监控客户端MOVED/ASK重定向频率,超过阈值时暂停迁移。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/rediscluster-fen-pian-lu-you-ji-zhi-yu-kua-cao-wei-shi-wu/