現(xiàn)象:
Springboot 接入redis后發(fā)現(xiàn)隔一段時(shí)間連接會(huì)超時(shí) 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內(nèi)置了兩款驅(qū)動(dòng),jedis和lettuce。springboot1.X版本默認(rèn)jedis實(shí)現(xiàn),springboot2.X默認(rèn)lettuce實(shí)現(xiàn)。
lettuce:基于netty實(shí)現(xiàn),線程安全,但默認(rèn)只有一個(gè)實(shí)例。
jedis:直連redis服務(wù)端,一般配合連接池使用,可以增加物理連接。
lettuce只要斷掉連接后,自己并沒(méi)有去重連的機(jī)制,最后導(dǎo)致time out。
解決辦法:
方法一:
更改連接redis的連接方式,使用jedis連接
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- 過(guò)濾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: xxx
host: 172.16.0.x
port: 6579
timeout: 5000
jedis:
pool:
#最大連接數(shù)據(jù)庫(kù)連接數(shù),設(shè) 0 為沒(méi)有限制
max-active: 8
#最大等待連接中的數(shù)量,設(shè) 0 為沒(méi)有限制
max-idle: 8
#最大建立連接等待時(shí)間。如果超過(guò)此時(shí)間將接到異常。設(shè)為-1表示無(wú)限制。
max-wait: -1ms
#最小等待連接中的數(shù)量,設(shè) 0 為沒(méi)有限制
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}
方法二:
在開(kāi)發(fā)的時(shí)候,使用到Lettuce連接redis,一段時(shí)間后不操作,再去操作redis,會(huì)報(bào)連接超時(shí)錯(cuò)誤,在其重連后又可使用。
原因是:Lettuce 自適應(yīng)拓?fù)渌⑿拢ˋdaptive updates)與定時(shí)拓?fù)渌⑿拢≒eriodic updates) 是默認(rèn)關(guān)閉的導(dǎo)致問(wèn)題的出現(xiàn)
1、重寫(xiě)連接工廠實(shí)例,更改其
LettuceClientConfiguration 為開(kāi)啟拓?fù)涓?/p>
@Configuration
public class RedisConfig {
@Autowired
private RedisProperties redisProperties;
//這是固定的模板
//自己定義了一個(gè)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);
//解決序列化問(wèn)題
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連接工廠實(shí)現(xiàn)
* LettuceConnectionFactory實(shí)現(xiàn)了RedisConnectionFactory接口
* UVPV用Redis
*
* @return 返回LettuceConnectionFactory
*/
@Bean(destroyMethod = "destroy")
//這里要注意的是,在構(gòu)建LettuceConnectionFactory 時(shí),如果不使用內(nèi)置的destroyMethod,可能會(huì)導(dǎo)致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());
//我使用的是單機(jī)redis,集群使用上面注釋的代碼
Set<RedisNode> nodes = new HashSet<>();
nodes.add(new RedisNode(redisProperties.getHost(), redisProperties.getPort()));
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 包括線程池配置和安全項(xiàng)配置
*
* @param genericObjectPoolConfig common-pool2線程池
* @return lettuceClientConfiguration
*/
private LettuceClientConfiguration getLettuceClientConfiguration(GenericObjectPoolConfig genericObjectPoolConfig) {
/*
ClusterTopologyRefreshOptions配置用于開(kāi)啟自適應(yīng)刷新和定時(shí)刷新。如自適應(yīng)刷新不開(kāi)啟,Redis集群變更時(shí)將會(huì)導(dǎo)致連接異常!
*/
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
//開(kāi)啟自適應(yīng)刷新
//.enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT, ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)
//開(kāi)啟所有自適應(yīng)刷新,MOVED,ASK,PERSISTENT都會(huì)觸發(fā)
.enableAllAdaptiveRefreshTriggers()
// 自適應(yīng)刷新超時(shí)時(shí)間(默認(rèn)30秒)
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(25)) //默認(rèn)關(guān)閉開(kāi)啟后時(shí)間為30秒
// 開(kāi)周期刷新
.enablePeriodicRefresh(Duration.ofSeconds(20)) // 默認(rèn)關(guān)閉開(kāi)啟后時(shí)間為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監(jiān)控中查看
//.clientName(appName + "_lettuce")
.build();
}
}
2、SpringBoot2.3.x后,可使用配置文件中開(kāi)啟lettuce的拓?fù)渌⑿?/p>
lettuce:
pool:
max-active: 20
max-wait: -1ms
max-idle: 10
min-idle: 2
cluster:
refresh:
adaptive: true
#20秒自動(dòng)刷新一次
period: 20






