JedisException: Could not get a
错误堆栈信息
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisConfig': Invocation of init method failed; nested exception is redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:139)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1737)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:576)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
at com.ruoyi.RuoYiApplication.main(RuoYiApplication.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:51)
at redis.clients.jedis.ShardedJedisPool.getResource(ShardedJedisPool.java:36)
at com.ruoyi.redis.util.InitRedisUtil.getInstance(InitRedisUtil.java:106)
at com.ruoyi.redis.util.InitRedisUtil.init(InitRedisUtil.java:32)
at com.ruoyi.redis.config.RedisConfig.initRedis(RedisConfig.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:363)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:307)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136)
... 23 common frames omitted
Caused by: java.util.NoSuchElementException: Unable to validate object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:498)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:365)
at redis.clients.util.Pool.getResource(Pool.java:49)
... 34 common frames omitted
分析
上面的报错堆栈中,有两个Caused by,分别是
1、 Could not get a resource from the pool
2、java.util.NoSuchElementException: Unable to validate object
说的大概是一个意思,连接池中获取不到资源/找不到有效的对象。查看报错位置的源码,如下:
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
this.assertOpen();
AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) {
this.removeAbandoned(ac);
}
PooledObject<T> p = null;
boolean blockWhenExhausted = this.getBlockWhenExhausted();
long waitTime = System.currentTimeMillis();
while(true) {
boolean create;
do {
do {
do {
if (p != null) {
this.updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}
create = false;
p = (PooledObject)this.idleObjects.pollFirst();
if (p == null) {
p = this.create();
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0L) {
p = (PooledObject)this.idleObjects.takeFirst();
} else {
p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException("Timeout waiting for idle object");
}
} else if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
if (!p.allocate()) {
p = null;
}
} while(p == null);
try {
this.factory.activateObject(p);
} catch (Exception var13) {
try {
this.destroy(p);
} catch (Exception var12) {
;
}
p = null;
if (create) {
NoSuchElementException nsee = new NoSuchElementException("Unable to activate object");
nsee.initCause(var13);
throw nsee;
}
}
} while(p == null);
} while(!this.getTestOnBorrow() && (!create || !this.getTestOnCreate()));
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = this.factory.validateObject(p);
} catch (Throwable var15) {
PoolUtils.checkRethrow(var15);
validationThrowable = var15;
}
if (!validate) {
try {
this.destroy(p);
this.destroyedByBorrowValidationCount.incrementAndGet();
} catch (Exception var14) {
;
}
p = null;
if (create) {
NoSuchElementException nsee = new NoSuchElementException("Unable to validate object");
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
查看源码,发下报错的主要原因是上面源码中变量validate的值是false导致的,而左右这个变量的值的代码是
validate = this.factory.validateObject(p);
跟进去看源码:
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
BinaryJedis jedis = (BinaryJedis)pooledJedis.getObject();
try {
HostAndPort hostAndPort = (HostAndPort)this.hostAndPort.get();
String connectionHost = jedis.getClient().getHost();
int connectionPort = jedis.getClient().getPort();
return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort && jedis.isConnected() && jedis.ping().equals("PONG");
} catch (Exception var6) {
return false;
}
}
这个方法是判断连接有效性的,这里校验不通过,说明reids地址配置的有问题,当然,也可以通过配置设置,启动的时候不进行redis地址有效性的校验,配置如下:
// 在获取连接的时候检查有效性, 默认false
config.setTestOnBorrow(true);
将这个配置去掉,问题解决,当然,去掉的前提是你得保证自己配置的redis地址正确,配个错误地址,运营的时候才发现,也是不好的。
探究
配置config.setTestOnBorrow(true); 改为false,启动时这个错误不报了,但是出现了新错误,如下:
22:11:44.234 [restartedMain] ERROR c.r.r.u.InitRedisUtil - [setStringValue,192] - java.net.ConnectException: Connection refused (Connection refused)
redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused (Connection refused)
at redis.clients.jedis.Connection.connect(Connection.java:207)
at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:93)
at redis.clients.jedis.Connection.sendCommand(Connection.java:126)
at redis.clients.jedis.BinaryClient.set(BinaryClient.java:110)
at redis.clients.jedis.Client.set(Client.java:47)
at redis.clients.jedis.Jedis.set(Jedis.java:120)
at redis.clients.jedis.ShardedJedis.set(ShardedJedis.java:43)
at com.ruoyi.redis.util.InitRedisUtil.setStringValue(InitRedisUtil.java:190)
at com.ruoyi.redis.util.RedisUtil.setStringValue(RedisUtil.java:77)
at com.ruoyi.redis.util.RedisUtil.putDefaultInfo(RedisUtil.java:35)
at com.ruoyi.redis.runner.InitRedisCacheRunner.run(InitRedisCacheRunner.java:25)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:813)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:797)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:324)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
at com.ruoyi.RuoYiApplication.main(RuoYiApplication.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: java.net.ConnectException: Connection refused (Connection refused)
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at redis.clients.jedis.Connection.connect(Connection.java:184)
... 21 common frames omitted
我的连接地址配置如下,我可以确定我的ip和端口没有问题
redis.address=redis://root:123456@121.271.21.198:6379/0,redis://root:123456@121.271.21.198:6379/1,redis://root:123456@121.271.94.217:6379/2
上面的配置中,123456是redis密码,访问redis只需要密码,不需要用户名,但是这里却配置了用户名,这是因为程序在解析该地址的时候使用的URI进行解析,这个用户名密码是这个对象的解析标准服务器地址用的。我这里的root是随便写的,因为jedis使用url解析完配置之后,直接使用的是host,port,pwd,除了上面三个外,还有一个参数ssl,这个是根据地址前面前缀redis,匹配的,如果以rediss开头,则ssl为true,但是我之前配的都是redis,所以它这个参数配置的都是false,将地址中的redis改成rediss,重新运行服务,问题解决。
上面描述对应的源码如下:
/**
* 这里传入的参数实际上就是:redis://root:123456@121.271.21.198:6379/0
*/
public JedisShardInfo(String host) {
super(1);
this.password = null;
this.name = null;
this.db = 0;
URI uri = URI.create(host);
if (JedisURIHelper.isValid(uri)) {
this.host = uri.getHost();
this.port = uri.getPort();
this.password = JedisURIHelper.getPassword(uri);
this.db = JedisURIHelper.getDBIndex(uri);
this.ssl = uri.getScheme().equals("rediss");
} else {
this.host = host;
this.port = 6379;
}
}
public static String getPassword(URI uri) {
String userInfo = uri.getUserInfo();//root:123456
return userInfo != null ? userInfo.split(":", 2)[1] : null;//123456
}
public static int getDBIndex(URI uri) {
String[] pathSplit = uri.getPath().split("/", 2);
if (pathSplit.length > 1) {
String dbIndexStr = pathSplit[1];//0
return dbIndexStr.isEmpty() ? 0 : Integer.parseInt(dbIndexStr);//0
} else {
return 0;
}
}
从上面的源码知,jedis在初始化连接工厂的时候,解析地址参数并没有使用那个用户名。
SSL
Secure socket layer(SSL)安全协议主要用来提供对用户和服务器的认证;对传送的数据进行加密和隐藏;确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准。