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


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

  • redis
  • stop-writes-on-bgsav
  • validate categories: redis date: 2017-12-12 22:30:26

背景

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

比如在做session共享时 tomcat使用redis做session 参考github.com/jcoleman/to…

当然关于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迁移到单独服务器上!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值