線上碰到連接池無法獲取問題 ,排查後,看到配置redis的地方有問題,
RedisConnectionFailureException: Could not get a resource from the pool; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
修改如下
先說下解釋
# REDIS (RedisProperties)
# Redis數據庫索引(默認爲0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=localhost
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認爲空)
spring.redis.password=admin
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閒連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閒連接
spring.redis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=0
JAVA配置如下 主要是連接池空閒數和最大連接數,還有等待時間,默認連接數和等待數都是 8
之前可能就是因爲空閒等待的連接數300 太大了,導致連接獲取失敗,每 個服務連接300 有8臺服務器,就是2400連接數,巨坑
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;
@Value("${spring.redis.commandTimeout}")
private int commandTimeout;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.pool.max-active}")
private int maxActive;
@Bean
public JedisCluster getJedisCluster() {
String[] cNodes = clusterNodes.split(",");
Set<HostAndPort> nodes =new HashSet<>();
//分割出集羣節點
for(String node : cNodes) {
String[] hp = node.split(":");
nodes.add(new HostAndPort(hp[0],Integer.parseInt(hp[1])));
}
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
//創建集羣對象
// JedisCluster jedisCluster = new JedisCluster(nodes,commandTimeout);
return new JedisCluster(nodes, 1000, timeout, 1000,
password, jedisPoolConfig);
/**
* 設置數據存入redis 的序列化方式
*</br>redisTemplate序列化默認使用的jdkSerializeable,存儲二進制字節碼,導致key會出現亂碼,所以自定義
*序列化類
*
* @paramredisConnectionFactory
*/
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper =new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
原理如下
SpringBootStarterRedis 源碼分析
我們用 Spring Boot 都知道 starter 的原理(spring-boot-autoconfigure.jar 包裏面的 spring.factories 定義了 Spring Boot 默認加載的 AutoConfiguration),因此,打開 spring.factories 文件可以找到 Spring 自動加載了。
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,
這兩個 Configuration 類,我們先打開 RedisAutoConfiguration 的源碼 ,來一起看一下里面的關鍵代碼片段。
(1)代碼片段一:自動加載 JedisConnectionFactory。
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
通過這一段代碼可以看到,JedisConnectionFactory 可以自己配置也可以直接用 Spring Boot 給我們提供的默認配置。
(2)代碼片段二:查看 createJedisConnectionFactory() 的具體方法。
private JedisConnectionFactory createJedisConnectionFactory() {
//這裏會取我們配置文件裏面的配置,如果沒有配置,new 一個默認連接池
JedisPoolConfig poolConfig = this.properties.getPool() != null
? jedisPoolConfig() : new JedisPoolConfig();
//如果配置了Sentinel就取哨兵的配置直接返回
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
}
//如果沒有配置中Sentinel,而配置了Cluster切片的配置方法,它就取Cluster的配置方法
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
}
//默認取連接pool的配置方法
return new JedisConnectionFactory(poolConfig);
}
.......
//取配置文件裏面的Pool的配置
private JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig config = new JedisPoolConfig();
RedisProperties.Pool props = this.properties.getPool();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
return config;
}
.......
//JedisPoolConfig的類默認構造函數
public class JedisPoolConfig extends GenericObjectPoolConfig {
public JedisPoolConfig() {
this.setTestWhileIdle(true);
this.setMinEvictableIdleTimeMillis(60000L);
this.setTimeBetweenEvictionRunsMillis(30000L);
this.setNumTestsPerEvictionRun(-1);
}
}
最終還是用到連接
RedisConnectionFactory 就是特於JedisConnectionFactory 創建之後的連接,
所以後面的 RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>(); 模板方法使用方式是一樣的,這裏JedisConnection可以自己配置,如果不配置就是默認配置
另外項目的配置
redis.pass=
#超時時間
redis.timeout=2000
#最大空閒連接數
redis.maxIdle=100
#最小空閒連接數
redis.minIdle=50
#最大連接數
redis.maxTotal=500
#最大等待時間
redis.maxWait=1500
#使用連接時,檢測連接是否成功
redis.testOnBorrow=true
#返回連接時,檢測連接是否成功
redis.testOnReturn=true
@Configuration
@PropertySource(value = "classpath:/redis.properties")
public class RedisConfiguration
{
@Bean(name = "jedis.cluster")
@Autowired
public JedisCluster jedisCluster(
@Qualifier("jedis.pool.config") GenericObjectPoolConfig config,
@Value("${redis.host.1}") String host_1,
@Value("${redis.port.1}") int port_1,
@Value("${redis.host.2}") String host_2,
@Value("${redis.port.2}") int port_2,
@Value("${redis.host.3}") String host_3,
@Value("${redis.port.3}") int port_3,
@Value("${redis.host.4}") String host_4,
@Value("${redis.port.4}") int port_4,
@Value("${redis.host.5}") String host_5,
@Value("${redis.port.5}") int port_5,
@Value("${redis.host.6}") String host_6,
@Value("${redis.port.6}") int port_6,
@Value("${redis.timeout}") int timeout)
{
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort(host_1, port_1));
nodes.add(new HostAndPort(host_2, port_2));
nodes.add(new HostAndPort(host_3, port_3));
nodes.add(new HostAndPort(host_4, port_4));
nodes.add(new HostAndPort(host_5, port_5));
nodes.add(new HostAndPort(host_6, port_6));
//zhangfeifei123!!==
return new JedisCluster(nodes, 1000, timeout, 1000,
"1qOaaqes", config);
}
@Bean(name = "jedis.pool.config")
public GenericObjectPoolConfig jedisPoolConfig(
@Value("${redis.maxTotal}") int maxTotal,
@Value("${redis.maxIdle}") int maxIdle,
@Value("${redis.minIdle}") int minIdle,
@Value("${redis.maxWait}") int maxWaitMillis,
@Value("${redis.testOnBorrow}") boolean testOnBorrow,
@Value("${redis.testOnReturn}") boolean testOnReturn)
{
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
return config;
}
}