Redis问题处理:JedisConnectionException: Could not get a resource from the pool

Redis问题处理:JedisConnectionException: Could not get a resource from the pool

简要分析

分析版本:redis.clients:jedis:2.9.0

异常信息抛出的位置位于redis.clients.util.Pool.getResource

  public T getResource() {
    try {
      return internalPool.borrowObject();
    } catch (NoSuchElementException nse) {
      throw new JedisException("Could not get a resource from the pool", nse);
    } catch (Exception e) {
      throw new JedisConnectionException("Could not get a resource from the pool", e);
    }
  }

redis.clients.util.Pool.getResource会从 JedisPool 实例池中返回一个可用的 redis 连接。分析源码可知 JedisPool 继承了 redis.clients.util.Pool,而这个 Pool 是通过 commons-pool 开源工具包中的org.apache.commons.pool2.impl.GenericObjectPool来实现对 Jedis 实例的管理的。所以我们可以先分析一下 GenericObjectPool。

public class JedisPool extends Pool<Jedis> {}

public abstract class Pool<T> implements Closeable {
  protected GenericObjectPool<T> internalPool;
}

其中GenericObjectPool三个重要个几个属性是:

// 最大连接数
private volatile int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE;//8
// 最大空闲数
private volatile int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE;//0
// 最大等待时间,单位毫秒(million seconds)
private volatile long maxWaitMillis = BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;//-1

参数之间的关系为:连接池中无可用连接时会会进行等待maxWait时间,若超出泽抛Could not get a resource from the pool异常。所以应根据程序实际情况合理设置这三个参数的值,尽量避免这个异常。

如果连接池没有可用 Jedis 连接,会等待 maxWaitMillis(毫秒),依然没有获取到可用 Jedis 连接,会抛出这个异常:redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

异常可能性分析

1、客户端没有从连接池(最大maxTotal个)拿到可用 Jedis 连接造成的【连接泄露】(常见)

分析图如下:
在这里插入图片描述

验证:JedisPool 默认的maxTotal=8,下面的代码从 JedisPool 中借了8次 Jedis,但是没有归还,当第9次(jedisPool.getResource().ping())时,就会抛出异常:

public static void main(String[] args) {
  GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
  poolConfig.setMaxWaitMillis(1000);
  JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 10, "******");
  //向JedisPool借用8次连接,但是没有执行归还操作。
  for (int i = 0; i < 8; i++) {
    Jedis jedis;
    try {
      jedis = jedisPool.getResource();
      String ping = jedis.ping();
      System.out.println(ping);
    } catch (Exception e) {
      System.out.println(e);
    }
  }
  System.out.println("向JedisPool借用8次连接完成");
  String ping = jedisPool.getResource().ping();
  System.out.println(ping);
}

所以推荐使用的代码规范是:

Jedis jedis = null;
try {
    jedis = jedisPool.getResource();
    //具体的命令
    jedis.executeCommand()
} catch (Exception e) {
    //如果命令有key最好把key也在错误日志打印出来,对于集群版来说通过key可以帮助定位到具体节点。
    logger.error(e.getMessage(), e);
} finally {
    //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
    if (jedis != null) {
        jedis.close();
    }
}

2、业务并发量大,maxTotal 值太小

示例:一次命令时间(borrow|return resource + Jedis执行命令(含网络) )的平均耗时约为 1ms,一个连接的QPS大约是1000,业务期望的 QPS 是 80000,那么理论上需要的资源池大小是 80000 / 1000 = 80个,实际 maxTotal 可以根据理论值合理进行微调。

调节 maxTotal 值的时候,需要考虑对应服务的机器配置,可参考 JedisPool资源池优化

3、Jedis 连接归还太慢

示例:Redis发生了阻塞(例如慢查询等原因),所有连接在超时时间范围内等待,并发量较大时,会造成连接池资源不足。

避免一些 big key 的查询,批量获取数据的时候,记得分页。

4、其他问题

示例:丢包、DNS、客户端TCP参数配置等,可参考 Jedis常见问题分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值