redis无法获取连接Could not get a resource from the pool分析


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1588803,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

背景

随着系统使用用户上升,我们也愈发多的使用到了redis组件!

比如在做session共享时 tomcat使用redis做session 参考https://github.com/jcoleman/tomcat-redis-session-manager

当然关于session共享在tomcat中使用存在一些限制

  1. tomcat指定版本
  2. web应用使用jedis和common pools 需要指定版本 容易出现jar冲突
  3. 运维配置对应redis信息 连接池开发无感

因此更多可以考虑使用spring-session通过redis来管理session

当然我们目前的场景使用shiro做session管理【shiro可以委托给容器或者第三方组件】

那么当使用redis组件多了的场景我们就极容易碰到如下的错误

Could not get a resource from the pool

简要分析一下我们系统中发生该错误的场景

分析

我们在某些场景下报错如下

        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)         at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)         at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)         at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)         at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)         at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)         at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:442)         at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1082)         at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:623)         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1756)         at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1715)         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)         at java.lang.Thread.run(Thread.java:745) Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool         at redis.clients.util.Pool.getResource(Pool.java:50)         at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)         at net.oschina.j2cache.redis.support.RedisSingleFactory.getResource(RedisSingleFactory.java:23)         at net.oschina.j2cache.redis.support.RedisSingleFactory.getResource(RedisSingleFactory.java:12)         at net.oschina.j2cache.redis.RedisCacheProxy.getResource(RedisCacheProxy.java:47)         at net.oschina.j2cache.redis.RedisCacheProxy.hset(RedisCacheProxy.java:68)         at net.oschina.j2cache.redis.RedisCache.put(RedisCache.java:158)         ... 79 common frames omitted Caused by: java.util.NoSuchElementException: Unable to validate object         at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:506)         at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)         at redis.clients.util.Pool.getResource(Pool.java:48)         ... 85 common frames omitted

一个关键词出现在了‘Unable to validate object’堆栈中!

在对应的连接池代码中可以看到

  if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {         boolean validate = false;         Throwable validationThrowable = null;         try {             validate = factory.validateObject(p);         } catch (Throwable t) {             PoolUtils.checkRethrow(t);             validationThrowable = t;         }         if (!validate) {             try {                 destroy(p);                 destroyedByBorrowValidationCount.incrementAndGet();             } catch (Exception e) {                 // Ignore - validation failure is more important             }             p = null;             if (create) {                 NoSuchElementException nsee = new NoSuchElementException(                         "Unable to validate object");                 nsee.initCause(validationThrowable);                 throw nsee;             }         }     } }

由于我们redis连接池配置了validate

所以会在borrow之后执行validate操作

我们查看具体的validate操作代码

@Override public boolean validateObject(PooledObject<Jedis> pooledJedis) {   final BinaryJedis jedis = pooledJedis.getObject();   try {     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 (final Exception e) {     return false;   } }

很明显当拿到redis的连接之后需要执行ping指令

而当服务器正常的场景 redis将返回PONG

那么是什么情况导致我们可以连接到redis而又无法正常的ping该服务呢?

怀疑如下:

  1. 大型数据过期导致redis服务被占 比如某个hash过期
  2. 持久化失败使得写命令失败

我们知道 大key的过期或者试用类似于keys的指令时十分耗时 由于redis单进程的特性将会阻塞其他指令!

来查询一下报错日志 在短短时间内迅速报错

[root@iZ11to3arruZ logs]# grep "Unable to validate object" erp-error.log |wc -l 2357

如果大key过期的话那么应当会出现一会之后系统正常可以使用!

然而线上出现的问题是服务无法正常使用

因此考虑问题2

我们检查zabbix相关

当redis无法使用的时候恰好是内存最少的时候 当重新启动服务器时系统又可以正常使用!

那么考虑如下问题,是否是内存不足导致redis出现问题呢?

带着上述疑问 找到了相关说明!

查询到如下说明

stop-writes-on-bgsave-error

我们系统中使用 是yes 因此当内存不足的时候将无法序列化成文件

但是我们的内存明明还有接近2G呢!

Redis在保存数据到硬盘时为了避免主进程假死,需要Fork一份主进程,然后在Fork进程内完成数据保存到硬盘的操作,如果主进程使用了4GB的内存,Fork子进程的时候需要额外的4GB,此时内存就不够了,Fork失败,进而数据保存硬盘也失败了。

 

因此重启后由于redis重新load 使得内存占用小于原先!

解决方案

将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!将redis迁移到单独服务器上!!!

本文发表于2017年12月13日 00:33
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1588803,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 4249 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1