现象:
【springboot整合redis使用Lettuce客户端超时问题】Springboot 接入redis后发现隔一段时间连接会超时 command timed out 。
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)at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:273)at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:266)at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperations.java:57)at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:96)at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:53)
原因:
spring-data-redis内置了两款驱动,jedis和lettuce 。springboot1.X版本默认jedis实现,springboot2.X默认lettuce实现 。
lettuce:基于netty实现,线程安全,但默认只有一个实例 。
jedis:直连redis服务端,一般配合连接池使用,可以增加物理连接 。
lettuce只要断掉连接后,自己并没有去重连的机制,最后导致time out 。
解决办法:
方法一:
更改连接redis的连接方式,使用jedis连接
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions><!-- 过滤lettuce,使用jedis作为redis客户端 --><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion> </exclusions></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId></dependency><dependency> <groupId>org.Apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency>
配置文件
spring:redis:password: xxxhost: 172.16.0.xport: 6579timeout: 5000jedis:pool:#最大连接数据库连接数,设 0 为没有限制max-active: 8#最大等待连接中的数量,设 0 为没有限制max-idle: 8#最大建立连接等待时间 。如果超过此时间将接到异常 。设为-1表示无限制 。max-wait: -1ms#最小等待连接中的数量,设 0 为没有限制min-idle: 0#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}
方法二:
在开发的时候,使用到Lettuce连接redis,一段时间后不操作,再去操作redis,会报连接超时错误,在其重连后又可使用 。
原因是:Lettuce 自适应拓扑刷新(Adaptive updates)与定时拓扑刷新(Periodic updates) 是默认关闭的导致问题的出现
1、重写连接工厂实例,更改其
LettuceClientConfiguration 为开启拓扑更新
@Configurationpublic class RedisConfig {@Autowiredprivate 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序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);//hash的value序列化方式采用jacksontemplate.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());//我使用的是单机redis,集群使用上面注释的代码Set<RedisNode> nodes = new HashSet<>();nodes.add(new RedisNode(redisProperties.getHost(), redisProperties.getPort()));RedisStandaloneConfigurationredisStandaloneConfiguration=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();} }
推荐阅读
- SpringBoot整合MybatisPlus基本的增删改查,保姆级教程
- Redis缓存之String的滥用
- 一文讲清楚SpringBoot六种读取配置方式
- 安装Redis
- 接口的屏蔽和限流很难么?Redis全搞定
- Redis有哪些慢操作?
- 一个常用的RedisHelper类
- SpringBoot整合MybatisPlus数据自动填充
- Redis 官方可视化工具,功能真心强大
- Redis和Mysql数据怎么保持一致