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

背景

随着系统使用用户上升,我们也愈发多的使用到了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迁移到单独服务器上!!!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏WindCoder

微信小程序踩坑记-Java基于SSM下的post请求

最近在持续踩微信小程序的坑,canvas和WebSocket的暂时还没找到相关的解决方案,暂时先将post请求无法获取data参数的坑填上。直接附上解决方案,已...

7701
来自专栏GopherCoder

『No19: Gorm 上手指南』

如果你是做后端开发的,日常工作中,除了熟悉编程语言之外,数据库怕是最常用的技术了吧。

5511
来自专栏软件工程师成长笔记

CXF实现webService服务

Web Service也叫XML Web Service,WebService是一种可以接收从Internet或者Internet上的其它系统中传递过来的请求,...

1653
来自专栏你不就像风一样

网络爬虫之Url含有中文如何转码

1032
来自专栏SDNLAB

Open vSwitch源码解析之基于VxLAN实现NSH解析功能

1. NSH-SFC概述 当前SFC的实现方案主要分为两种:一种基于NSH(network service header)。数据封装时,在L2或者L3数据后添加...

5855
来自专栏Seebug漏洞平台

CVE-2017-16943 Exim UAF漏洞分析

感恩节那天,meh在Bugzilla上提交了一个exim的uaf漏洞:https://bugs.exim.org/show_bug.cgi?id=2199,这周...

4145
来自专栏小灰灰

Java & PhantomJs 实现html输出图片

Java & PhantomJs 实现html输出图片 借助phantomJs来实现将html网页输出为图片 I. 背景 如何在小程序里面生成一张图,分享到朋...

6458
来自专栏Java架构师历程

Struts2的工作原理

在学习struts2之前,首先我们要明白使用struts2的目的是什么?它能给我们带来什么样的好处?

934
来自专栏Seebug漏洞平台

CVE-2017-16943 Exim UAF漏洞分析

作者:Hcamael@知道创宇404实验室 感恩节那天,meh在Bugzilla上提交了一个exim的uaf漏洞:https://bugs.exim.org/s...

3896
来自专栏Java后端技术栈

【面试题】2018年最全Java面试通关秘籍第三套!

注:本文是从众多面试者的面试经验中整理而来,其中不少是本人出的一些题目,网络资源众多,如有雷同,纯属巧合!禁止一切形式的碰瓷行为!未经允许禁止一切形式的转载和复...

1221

扫码关注云+社区