一、问题描述
Jedis version: 2.9.3 (问题版本)
压测代码片段:
// 初始化
ShardedJedisPool createShardedPool = new ShardedJedisPool(this.jedisPoolConfig, this.jedisShardInfos);
// 遍历get方法
ShardedJedis jedis = null
try {
jedis = createShardedPool.getConnection();
jedis.get(rediskey.getBytes());
} finally {
if (jedis != null) {
jedis.close();
}
}
redis异常:
error.kind:
redis.clients.jedis.exceptions.JedisException
message:
Could not get a resource from the pool
stack:
redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:51)
at redis.clients.jedis.ShardedJedisPool.getResource(ShardedJedisPool.java:36)
二、原因分析:
1、通过压测发现连接 dataSource 存在null情况
2、通过jedis官网issue找到问题原因
https://github.com/redis/jedis/issues/1920
Jedis.java
@Override
public void close() {
if (dataSource != null) {
if (client.isBroken()) {
this.dataSource.returnBrokenResource(this);
} else {
this.dataSource.
returnResource(this);
}
this.dataSource = null; // <-- This line
} else {
super.close();
}
}
JedisSentinelPool.java
@Override
public Jedis getResource() {
while (true) {
Jedis jedis = super.getResource();
jedis.setDataSource(this); // <-- This line
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient()
.getPort());
if (master.equals(connection)) {
// connected to the correct master
return jedis;
} else {
returnBrokenResource(jedis);
}
}
}
In the case of concurrency
Thread A return an object but not run to “this.dataSource = null” yet
Thread B borrow an object and set dataSource to this;
And Then Thread A run this.dataSource = null;
Finally Thread A will never returnResource because dataSource is null
Jedis version:
2.10.0
Jedis version: 2.10.2 官方代码优化:
Jedis.java
public void close() {
if (this.dataSource != null) {
Pool<Jedis> pool = this.dataSource;
this.dataSource = null;
if (this.client.isBroken()) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
super.close();
}
}
但是 redis.clients.jedis.ShardedJedis#close 没有优化:
@Override
public void close() {
if (dataSource != null) {
boolean broken = false;
for (Jedis jedis : getAllShards()) {
if (jedis.getClient().isBroken()) {
broken = true;
break;
}
}
if (broken) {
dataSource.returnBrokenResource(this);
} else {
dataSource.returnResource(this);
}
this.dataSource = null;
} else {
disconnect();
}
}
最新的3.5.1 redis.clients.jedis.ShardedJedis#close 已经优化:
@Override
public void close() {
if (dataSource != null) {
boolean broken = false;
for (Jedis jedis : getAllShards()) {
if (jedis.isBroken()) {
broken = true;
break;
}
}
ShardedJedisPool pool = this.dataSource;
this.dataSource = null;
if (broken) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
disconnect();
}
}
三、 解决方案:
结果发现升级到最新的2.X => 2.10.2 本不能解决ShardedJedis 只能解决Jedis连接回收问题,最终升级到3.5.1 解决了ShardedJedis 高并发情况下没有回收连接池问题。
四、心得总结:
如果怀疑是redis sdk问题,应该第一时间去看看开源的issue,而不是自己去压测debug 浪费了很多时间;自己发现了没有回收的问题也是一度怀疑是这几行代码的问题,就是没有充分证据说明是sdk bug。最后感谢开源的力量。
https://github.com/redis/jedis/issues/1920