Redis Cluster 概览以及Spring Boot 中的使用
Redis Cluster 概览 - 目的:水平扩展+高可用。数据按 16384 个 slot 分片(slot = CRC16(key) % 16384),各主节点持有一部分 slot;每个主节点有 0..N 个从节点。
- 客户端路由:请求发到任意节点,若不在本节点,该节点返回 MOVED/ASK,客户端重定向到负责该 slot 的主节点(“智能客户端”负责处理)。
- 容错:主从复制+投票。超过半数主节点存活即可工作;某主节点失联时,对应从节点自动升级为主(failover)。
- 伸缩:迁移 slot(reshard/rebalance)即可;迁移期间会返回 ASK。
- 端口:客户端端口 P(如 6379),集群总线端口 P+10000(gossip/投票)。
- 重要限制
- 只有 DB 0,没有 SELECT 切库。
- 多 key 命令/事务/Lua 只能作用于同一 slot;否则报 CROSSSLOT。用哈希标签强制同槽:key 形如 {tag}:field1 与 {tag}:field2。
- Pub/Sub 是“节点局部”的;要全局广播需在所有节点订阅或用更高层库(如 Redisson)。
- 不支持 KEYS 全量扫描(每节点自己扫);SCAN 需逐节点汇总。
常用管理 - 创建集群
- 多台机器各启动一个 redis(开启 cluster-enabled yes,设置 cluster-config-file 和 port)
- 执行:redis-cli --cluster create host1:6379 host2:6379 host3:6379 host4:6379 host5:6379 host6:6379 --cluster-replicas 1
- 查看/维护
- redis-cli -c -h host -p 6379
- CLUSTER NODES / CLUSTER INFO
- 迁移 slot:redis-cli --cluster reshard host:6379
- 添加/删除节点:redis-cli --cluster add-node / del-node
Spring Boot 中的使用(Lettuce,推荐) - spring:
- redis:
- password: yourpwd
- timeout: 3000ms
- cluster:
- nodes:
- - 10.0.0.1:6379
- - 10.0.0.2:6379
- - 10.0.0.3:6379
- - 10.0.0.4:6379
- max-redirects: 5
- lettuce:
- pool:
- max-active: 16
- max-idle: 8
- min-idle: 0
- cluster:
- refresh:
- adaptive: true # 探测 MOVED/FAIL 后自动刷新拓扑
- period: 30s # 周期刷新拓扑
复制代码- import org.springframework.context.annotation.*;
- import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
- import org.springframework.data.redis.connection.*;
- import org.springframework.data.redis.connection.lettuce.*;
- import io.lettuce.core.cluster.ClusterClientOptions;
- import io.lettuce.core.cluster.topology.*;
- @Configuration
- public class RedisClusterConfig {
- @Bean
- public LettuceConnectionFactory redisConnectionFactory(RedisProperties props) {
- RedisClusterConfiguration cluster = new RedisClusterConfiguration(props.getCluster().getNodes());
- cluster.setMaxRedirects(props.getCluster().getMaxRedirects());
- if (props.getPassword()!=null) cluster.setPassword(RedisPassword.of(props.getPassword()));
- ClusterTopologyRefreshOptions topo = ClusterTopologyRefreshOptions.builder()
- .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT,
- ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
- .enablePeriodicRefresh(java.time.Duration.ofSeconds(30))
- .build();
- LettuceClientConfiguration client = LettuceClientConfiguration.builder()
- .clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topo).build())
- .commandTimeout(java.time.Duration.ofSeconds(3))
- .build();
- return new LettuceConnectionFactory(cluster, client);
- }
- }
复制代码- RedisTemplate(JSON 与二进制;注意:Cluster 没有多 DB,两个模板共用同一工厂)
- import org.springframework.context.annotation.*;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.connection.RedisConnectionFactory;
- import org.springframework.data.redis.serializer.*;
- @Configuration
- public class RedisTemplatesConfig {
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory cf) {
- RedisTemplate<String, Object> t = new RedisTemplate<>();
- t.setConnectionFactory(cf);
- t.setKeySerializer(StringRedisSerializer.UTF_8);
- t.setHashKeySerializer(StringRedisSerializer.UTF_8);
- GenericJackson2JsonRedisSerializer json = new GenericJackson2JsonRedisSerializer();
- t.setValueSerializer(json);
- t.setHashValueSerializer(json);
- t.afterPropertiesSet();
- return t;
- }
- @Bean(name = "binaryRedisTemplate")
- public RedisTemplate<String, byte[]> binaryRedisTemplate(RedisConnectionFactory cf) {
- RedisTemplate<String, byte[]> t = new RedisTemplate<>();
- t.setConnectionFactory(cf);
- t.setKeySerializer(StringRedisSerializer.UTF_8);
- t.setHashKeySerializer(StringRedisSerializer.UTF_8);
- t.setValueSerializer(RedisSerializer.byteArray());
- t.setHashValueSerializer(RedisSerializer.byteArray());
- t.afterPropertiesSet();
- return t;
- }
- }
复制代码- 关键点/最佳实践
- 不能用不同 DB 索引区分数据;用 key 前缀区分业务,如 app:bin:{tag}:...
- 需要对同一业务对象的多 key 操作(MGET/MSET、Lua、事务),把相关 key 放同槽:使用哈希标签
- 示例:"{order:123}:status" 与 "{order:123}:total" 在同一 slot,可一起 MGET/MSET。
- 分布式锁/延迟队列等,优先用 Redisson(对 Cluster 细节做了适配)。
- Pub/Sub 全局广播:在所有节点订阅,或用 Redisson 的 topic。
- 观察指标:CLUSTER INFO(cluster_state, slots_assigned, slots_ok, slots_pfail), INFO replication、latency、connected_clients。
Redisson(可选,高层封装) - // 仅示例
- org.redisson.config.Config c = new org.redisson.config.Config();
- c.useClusterServers()
- .addNodeAddress("redis://10.0.0.1:6379","redis://10.0.0.2:6379","redis://10.0.0.3:6379")
- .setPassword("yourpwd");
- org.redisson.api.RedissonClient redisson = org.redisson.Redisson.create(c);
- // 分布式锁
- org.redisson.api.RLock lock = redisson.getLock("{order:123}:lock");
- lock.lock();
- try { /* ... */ } finally { lock.unlock(); }
复制代码排错提示 - 报 CROSSSLOT:相关 key 不同 slot;用 {tag} 使其同槽。
- 报 MOVED/ASK 循环:客户端未启用 cluster 模式或拓扑未刷新;确保使用 Lettuce/Jedis 的 cluster 客户端,并开启拓扑刷新。
- 命令不支持:Cluster 不支持 KEYS/SCAN 全局;改为逐节点遍历或用二级索引。
|