关于SpringBoot整合redis使用Lettuce客户端超时问题
问题起因
使用到Lettuce连接redis,一段时间后不操作,再去操作redis,会报连接超时错误,在其重连后又可使用。
原因是:Lettuce 自适应拓扑刷新(Adaptive updates)与定时拓扑刷新(Periodic updates) 是默认关闭的
解决的方案
三种方案任选其一,我选的是第二个
1、重写连接工厂实例,更改其LettuceClientConfiguration 为开启拓扑更新
@Configuration public class RedisConfig { @Autowired private RedisProperties redisProperties; //这是固定的模板 //自己定义了一个RedisTemplate @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(@Qualifier("lettuceConnectionFactoryUvPv") RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); 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; } /** * 为RedisTemplate配置Redis连接工厂实现 * LettuceConnectionFactory实现了RedisConnectionFactory接口 * UVPV用Redis * * @return 返回LettuceConnectionFactory */ @Bean(destroyMethod = "destroy") //这里要注意的是,在构建LettuceConnectionFactory 时,如果不使用内置的destroyMethod,可能会导致Redis连接早于其它Bean被销毁 public LettuceConnectionFactory lettuceConnectionFactoryUvPv() throws Exception { List<String> clusterNodes = redisProperties.getCluster().getNodes(); Set<RedisNode> nodes = new HashSet<>(); clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.parseInt(address.split(":")[1])))); RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); clusterConfiguration.setClusterNodes(nodes); clusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword())); clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects()); RedisStandaloneConfiguration redisStandaloneConfiguration=new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(redisProperties.getHost()); redisStandaloneConfiguration.setPassword(redisProperties.getPassword()); redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase()); redisStandaloneConfiguration.setPort(redisProperties.getPort()); GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle()); poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle()); poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive()); return new LettuceConnectionFactory(redisStandaloneConfiguration, getLettuceClientConfiguration(poolConfig)); } /** * 配置LettuceClientConfiguration 包括线程池配置和安全项配置 * * @param genericObjectPoolConfig common-pool2线程池 * @return lettuceClientConfiguration */ private LettuceClientConfiguration getLettuceClientConfiguration(GenericObjectPoolConfig genericObjectPoolConfig) { /* ClusterTopologyRefreshOptions配置用于开启自适应刷新和定时刷新。如自适应刷新不开启,Redis集群变更时将会导致连接异常! */ ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() //开启自适应刷新 //.enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS) //开启所有自适应刷新,MOVED,ASK,PERSISTENT都会触发 .enableAllAdaptiveRefreshTriggers() // 自适应刷新超时时间(默认30秒) .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(25)) //默认关闭开启后时间为30秒 // 开周期刷新 .enablePeriodicRefresh(Duration.ofSeconds(20)) // 默认关闭开启后时间为60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60 .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2)) .build(); return LettucePoolingClientConfiguration.builder() .poolConfig(genericObjectPoolConfig) .clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build()) //将appID传入连接,方便Redis监控中查看 //.clientName(appName + "_lettuce") .build(); } }
2、SpringBoot2.3.x后,可使用配置文件中开启lettuce的拓扑刷新
lettuce: pool: max-active: 20 max-wait: -1ms max-idle: 10 min-idle: 2 cluster: refresh: adaptive: true #20秒自动刷新一次 period: 20
3、更改连接redis的连接方式,使用jedis连接
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
spring: redis: jedis: pool: max-active: ${redis.config.maxTotal:1024} max-idle: ${redis.config.maxIdle:50} min-idle: ${redis.config.minIdle:1} max-wait: ${redis.config.maxWaitMillis:5000} #lettuce: #pool: #max-active: ${redis.config.maxTotal:1024} #max-idle: ${redis.config.maxIdle:50} #min-idle: ${redis.config.minIdle:1} #max-wait: ${redis.config.maxWaitMillis:5000}