redis连接释放问题记录

redis连接释放问题记录

记录一次在压测后发现的redisTemplate使用场景下,redis的连接资源没有释放的问题。

文章目录

    • redis连接释放问题记录
      • 问题描述
      • 问题追踪
      • 问题定位
      • 问题解决

问题描述

springboot 版本:2.1.2(排除了lettuce的依赖)

jedis版本:2.9.1

场景:高并发情况下,RedisTemplate获取连接失败并阻塞线程导致TPS下降。

异常描述:如果设置了max-wait则在等待时间到后抛出异常:Timeout waiting for idle object。如果没有设置,则线程将被一直阻塞。

问题追踪

public T borrowObject(long borrowMaxWaitMillis) throws Exception {
     
    //.....
                                p = (PooledObject)this.idleObjects.takeFirst();
                            } else {
     
                                p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
                            }
                        }

                        if (p == null) {
     
                            throw new NoSuchElementException("Timeout waiting for idle object");
                        }
                    //.......
}

阻塞点在this.idleObjects.takeFirst()方法,如果设置了等待时间,则调用pollFirst()。debug可以发现,资源没有释放。

这个可以看当前对象中的borrowedCount和returnedCount发现,borrowedCount大于returnedCount。

borrowedCount:借用的资源计数

returnedCount:归还的资源计数

问题定位

borrowObject(long borrowMaxWaitMillis):获取资源的方法

returnObject(T obj):归还资源的方法

本来以为是归还资源的方法有bug导致的资源没有归还,但是经过多次debug分析后,发现问题点实际在于jedis的close方法上。

public void close() {
     
    if (this.dataSource != null) {
     
        if (this.client.isBroken()) {
     
            this.dataSource.returnBrokenResource(this);
        } else {
     
            this.dataSource.returnResource(this);
        }
		//问题点在于这里
        this.dataSource = null;
    } else {
     
        super.close();
    }
}

场景描述:

在多线程环境下,通过debug可以看到,当前jedis对象是共享的。

假如有线程1,2.

当线程1获取的资源的持有,线程2等待资源释放场景。

然后线程1在执行close时,通过returnResource释放了资源,而线程2拿到了资源。

然后线程2也执行到了close方法,而线程1执行了this.dataSource = null;

这时线程2则不会归还资源,直接执行了close,导致出现的连接没有释放问题。

问题解决

GitHub Issues描述

解决方式:

  1. 升级jedis版本或者回退jedis版本。
  2. 更换连接,可以考虑使用lettuce。

你可能感兴趣的:(redis)