问题:
使用lettuce连接Cluster集群实例,实例执行规格变更后,分片数有变化时,部分槽位(Slot)会迁移到新分片上,当客户端连接到新分片时会出现以下异常问题
java.lang.IllegalArgumentException: Connection to 100.123.70.194:6379 not allowed. This connection point is not known in the cluster view exceptionStackTrace io.lettuce.core.cluster.PooledClusterConnectionProvider.getConnectionAsync(PooledClusterConnectionProvider.java:359) io.lettuce.core.cluster.ClusterDistributionChannelWriter.write(ClusterDistributionChannelWriter.java:93) io.lettuce.core.cluster.ClusterCommand.complete(ClusterCommand.java:56) io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:563) io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:516)
org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 5 second(s) at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
问题
redis连接后,隔一段时间,连接慢
Redis Cluster集群模式下master宕机或网络抖动等原因,主从切换期间 报错:Redis command timed out等问题
Connection to X not allowed. This connection point is not known in the cluster view.
解决方案
开启redis client的集群拓扑刷新功能,不同客户端,采用不同处理方式
- jedis client默认自动支持(由于jedis通过自身异常反馈来识别重连、刷新服务端的集群信息机制,保证其自动故障恢复)
- luttuce client 默认未开启,需要手动开启刷新
- springboot 1.x之前版本默认使用jedis,无需要手动开启刷新
- springboot 2.x,redis client默认为Lettuce,默认不支持拓扑刷新解决方案:
- 使用jedis,不需要手动指定开启刷新
- 使用lettuce,需要设置开启刷新节点拓扑策略
- springboot 2.3.0开始,支持集群拓扑刷新功能,属性配置开启即可
springboot 2.0.X
方法一
使用letture方式连接redis,需要设置开启刷新节点拓扑刷新策略
使用 RedisClusterClient.reloadPartitions 自动 reload pattitions
#redis cluster config #RedisCluster集群节点及端口信息 spring.redis.cluster.nodes=192.168.50.29:6380,192.168.50.29:6381,192.168.50.29:6382,192.168.50.29:6383,192.168.50.29:6384,192.168.50.29:6385 #Redis密码 spring.redis.password= #在群集中执行命令时要遵循的最大重定向数目 spring.redis.cluster.max-redirects=6 #Redis连接池在给定时间可以分配的最大连接数。使用负值无限制 spring.redis.jedis.pool.max-active=1000 #以毫秒为单位的连接超时时间 spring.redis.timeout=2000 #池中“空闲”连接的最大数量。使用负值表示无限数量的空闲连接 spring.redis.jedis.pool.max-idle=8 #目标为保持在池中的最小空闲连接数。这个设置只有在设置max-idle的情况下才有效果 spring.redis.jedis.pool.min-idle=5 #连接分配在池被耗尽时抛出异常之前应该阻塞的最长时间量(以毫秒为单位)。使用负值可以无限期地阻止 spring.redis.jedis.pool.max-wait=1000 #redis cluster只使用db0 spring.redis.index=0
import io.lettuce.core.ReadFrom; import io.lettuce.core.cluster.ClusterClientOptions; import io.lettuce.core.cluster.ClusterTopologyRefreshOptions; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; /** * Redis配置类 * * @author 007 */ @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisConfiguration { @Autowired private RedisProperties redisProperties; @Bean(destroyMethod = "destroy") public LettuceConnectionFactory redisConnectionFactory() { // redis单节点 if (null == redisProperties.getCluster() || null == redisProperties.getCluster().getNodes()) { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()); configuration.setPassword(redisProperties.getPassword()); return new LettuceConnectionFactory(configuration); } // redis集群 RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes()); redisClusterConfiguration.setPassword(redisProperties.getPassword()); redisClusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects()); GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); genericObjectPoolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive()); genericObjectPoolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle()); genericObjectPoolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle()); genericObjectPoolConfig.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().getSeconds()); // 支持自适应集群拓扑刷新和动态刷新源 ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder() .enableAllAdaptiveRefreshTriggers() // 开启自适应刷新 .enableAdaptiveRefreshTrigger() // 开启定时刷新 .enablePeriodicRefresh(Duration.ofSeconds(5)) .build(); ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(clusterTopologyRefreshOptions).build(); /* ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(clusterTopologyRefreshOptions)//拓扑刷新 .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) .autoReconnect(true) .socketOptions(SocketOptions.builder().keepAlive(true).build()) .validateClusterNodeMembership(false)// 取消校验集群节点的成员关系 .build(); */ LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder() .poolConfig(genericObjectPoolConfig) .readFrom(ReadFrom.SLAVE_PREFERRED) // 使用ReadFrom.SLAVE_PREFERRED读写分离 .clientOptions(clusterClientOptions).build(); LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration); lettuceConnectionFactory.setShareNativeConnection(false);// 是否允许多个线程操作共用同一个缓存连接,默认 true,false 时每个操作都将开辟新的连接 lettuceConnectionFactory.resetConnection();// 重置底层共享连接, 在接下来的访问时初始化 return lettuceConnectionFactory; } //自定义一个RedisTemplate @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(@Qualifier("lettuceConnectionFactoryUvPv") LettuceConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); // 一定要注入RedisTemplate和redisTemplate.setConnectionFactory(lettuceConnectionFactory); template.setConnectionFactory(factory); //Json序列化配置 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(om.getPolymorphicTypeValidator()); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //解决序列化问题 om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); jackson2JsonRedisSerializer.setObjectMapper(om); //String的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); //hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); //value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); //hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }