Redis Cluster分片路由机制与跨槽位事务处理实战

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/

(0)
小编小编
上一篇 7小时前
下一篇 7小时前

相关推荐