项目场景:
这次是升级springBoot版本,然后启动的时候有一个服务的redis配置启动报错(其实我觉得应该是每个服务都会有这个问题,因为所有的服务redis的配置类我都是一样的,但是现在其他服务还是正常的,我也没有深究,因为在赶一个功能)
问题描述
启动的时候报错,redis的配置类报的:
从字面意思看应该是redis的客户端lettuce连接的时候的问题,redis在重新创建lettuce客户端链接资源的时候,之前的客户端链接资源没有被回收。
原因分析:
既然是lettuce客户端连接redis的配置类的问题,大致意思也知道就是资源回收的问题。我们用的springBoot,那么资源回收肯定应该是交给spring处理,这里出问题了,那就是没有交给spring处理呗。
下面看原先的redis整个配置类
import com.fillersmart.fsihouse.data.core.RedisUtil;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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单机配置
* 20210709 jedis替换为Lettuce,原因自己百度。
* @author zhengwen
*/
@Configuration
public class RedisOneConfig {
@Value("${redis.maxIdle}")
private Integer maxIdle;
@Value("${redis.maxTotal}")
private Integer maxTotal;
@Value("${redis.maxWaitMillis}")
private Integer maxWaitMillis;
@Value("${redis.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${redis.numTestsPerEvictionRun}")
private Integer numTestsPerEvictionRun;
@Value("${redis.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${redis.testOnBorrow}")
private boolean testOnBorrow;
@Value("${redis.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.cluster.max-redirects}")
private Integer mmaxRedirectsac;
@Value("${redis.hostName}")
private String hostName;
@Value("${redis.port}")
private int port;
@Value("${redis.password}")
private String password;
@Value("${redis.minIdle}")
private int minIdle;
/**
* redis连接池配置信息
* @return
*/
@Bean
public GenericObjectPoolConfig genericObjectPoolConfig() {
//连接池配置
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxTotal);
genericObjectPoolConfig.setMaxWaitMillis(maxWaitMillis);
return genericObjectPoolConfig;
}
/**
* 单机版本配置
* @param genericObjectPoolConfig
* @return
*/
@Bean
public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig genericObjectPoolConfig) {
//redis配置
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
//设置redis服务器的host或者ip地址
redisStandaloneConfiguration.setHostName(hostName);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(password);
//redis客户端配置
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder
builder = LettucePoolingClientConfiguration.builder().poolConfig(genericObjectPoolConfig).commandTimeout(Duration.ofMillis(maxWaitMillis));
builder.shutdownTimeout(Duration.ofMillis(minEvictableIdleTimeMillis));
builder.poolConfig(genericObjectPoolConfig);
LettuceClientConfiguration lettuceClientConfiguration = builder.build();
//根据配置和客户端配置创建连接
LettuceConnectionFactory lettuceConnectionFactory = new
LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
/**
* 实例化 RedisTemplate 对象
*
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
/**
* 设置数据存入 redis 的序列化方式,并开启事务
*
* @param redisTemplate
* @param factory
*/
private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
//如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setStringSerializer(new StringRedisSerializer());
// 开启事务
redisTemplate.setEnableTransactionSupport(false);
redisTemplate.setConnectionFactory(factory);
}
/**
* 注入封装RedisTemplate
*
* @return RedisUtil
* @throws
* @Title: redisUtil
*/
@Bean(name = "redisUtil")
public RedisUtil redisUtil(RedisTemplate<String, Object> redisTemplate) {
RedisUtil redisUtil = new RedisUtil();
redisUtil.setRedisTemplate(redisTemplate);
return redisUtil;
}
}
解决方案:
方案的思路就是把这个资源的回收交给spring处理,修改后的代码:
import com.fillersmart.fsihouse.data.core.RedisUtil;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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单机配置
* 20210709 jedis替换为Lettuce,原因自己百度。
* @author zhengwen
*/
@Configuration
public class RedisOneConfig {
@Value("${redis.maxIdle}")
private Integer maxIdle;
@Value("${redis.maxTotal}")
private Integer maxTotal;
@Value("${redis.maxWaitMillis}")
private Integer maxWaitMillis;
@Value("${redis.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${redis.numTestsPerEvictionRun}")
private Integer numTestsPerEvictionRun;
@Value("${redis.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${redis.testOnBorrow}")
private boolean testOnBorrow;
@Value("${redis.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.cluster.max-redirects}")
private Integer mmaxRedirectsac;
@Value("${redis.hostName}")
private String hostName;
@Value("${redis.port}")
private int port;
@Value("${redis.password}")
private String password;
@Value("${redis.minIdle}")
private int minIdle;
/**
* shutdown方法
* @return
*/
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(ClientResources.class)
public DefaultClientResources lettuceClientResources() {
return DefaultClientResources.create();
}
/**
* redis连接池配置信息
* @return
*/
@Bean
public GenericObjectPoolConfig genericObjectPoolConfig() {
//连接池配置
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxTotal);
genericObjectPoolConfig.setMaxWaitMillis(maxWaitMillis);
return genericObjectPoolConfig;
}
/**
* 单机版本配置
* @param genericObjectPoolConfig
* @return
*/
@Bean
public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig genericObjectPoolConfig,DefaultClientResources lettuceClientResources) {
//redis配置
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
//设置redis服务器的host或者ip地址
redisStandaloneConfiguration.setHostName(hostName);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(password);
//redis客户端配置
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder
builder = LettucePoolingClientConfiguration.builder().clientResources(lettuceClientResources).poolConfig(genericObjectPoolConfig).commandTimeout(Duration.ofMillis(maxWaitMillis));
builder.shutdownTimeout(Duration.ofMillis(minEvictableIdleTimeMillis));
builder.poolConfig(genericObjectPoolConfig);
LettuceClientConfiguration lettuceClientConfiguration = builder.build();
//根据配置和客户端配置创建连接
LettuceConnectionFactory lettuceConnectionFactory = new
LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
/**
* 实例化 RedisTemplate 对象
*
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
/**
* 设置数据存入 redis 的序列化方式,并开启事务
*
* @param redisTemplate
* @param factory
*/
private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
//如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setStringSerializer(new StringRedisSerializer());
// 开启事务
redisTemplate.setEnableTransactionSupport(false);
redisTemplate.setConnectionFactory(factory);
}
/**
* 注入封装RedisTemplate
*
* @return RedisUtil
* @throws
* @Title: redisUtil
*/
@Bean(name = "redisUtil")
public RedisUtil redisUtil(RedisTemplate<String, Object> redisTemplate) {
RedisUtil redisUtil = new RedisUtil();
redisUtil.setRedisTemplate(redisTemplate);
return redisUtil;
}
}
从上面可以看出,实际上就是做了3点:
1、增加了lettuceClientResources客户端资源由Spring容器管理
2、销毁的时候调用shutdown方法
3、将Spring管理的客户端资源bean传递给LettucePoolingClientConfiguration,加入到配置
到此就完美解决了,希望能帮到各位。