无法从连接池获取到Jedis连接是什么原因?如何解决?

无法从连接池获取到Jedis连接是什么原因?如何解决?

1.异常堆栈
(1) 连接池参数blockWhenExhausted = true(默认)
如果连接池没有可用Jedis连接,会等待maxWaitMillis(毫秒),如果依然没有获取到可用Jedis连接,会抛出如下异常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)

(2) 连接池参数blockWhenExhausted = false
设置该参数,如果连接池没有可用的Jedis连接,立即抛出异常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

Caused by: java.util.NoSuchElementException: Pool exhausted
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)

2.异常描述

上述异常是客户端没有从连接池(最大数量maxTotal)获得可用的Jedis连接造成的,可能有如下原因:
(1) 连接泄露 (较为常见)
JedisPool默认的maxTotal=8,下面的代码从JedisPool中获取了8个Jedis资源,但是没有归还资源,当第9次尝试获取Jedis资源的时候,无法获得(jedisPool.getResource().ping())

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
//向JedisPool借用8次连接,但是没有执行归还操作。
for (int i = 0; i 8; i++) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.ping();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
jedisPool.getResource().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是50000
那么理论上需要的资源池大小是50000 / 1000 = 50个,实际maxTotal可以根据理论值进行微调。
(3) Jedis连接阻塞
例如Redis发生了阻塞(例如慢查询等原因),所有连接在超时时间范围内等待,并发量较大时,会造成连接池资源不足。
(4) Jedis连接被拒绝
从池子里拿连接,由于没有空闲连接,需要重新生成一个Jedis连接,但是连接被拒绝:

可以从at redis.clients.jedis.Connection.connect(Connection.java:158)中看到实际是一个Socket连接:

socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// -@wjw_add
158: socket.connect(new InetSocketAddress(host, port), connectionTimeout);
说明:一般这种需要检查Redis的域名配置是否正确,排查该段时间网络是否正常。

(4) 其他问题
例如丢包、DNS、客户端TCP参数配置

3.解决方法
从上述分析,可以看出这个问题的原因比较复杂,请不要简单地认为连接池不够就盲目加大maxTotal,需要具体问题具体分析。

4.处理途径
请客户先按照上述表述进行问题定位和故障排除,如无法自行解决故障,请提交工单。

标签