Jedis连接池会资源泄露吗?

Jedis连接池竟然会资源泄露吗?

1.前言

Jedis是我们经常使用的Redis Java客户端.在SpringBoot2.X将lettuce作为默认Redis Java客户端之前,Jedis几乎是具备统治地位的.今天我会通过复盘一个压测时遇到的问题来解析Jedis 2.9.1版本一个必现的连接池资源泄露BUG.

2.问题描述

在某次压测中,某服务中产生了这样一条异常日志,期初我们猜测这可能是Jedis连接池负载较高,导致的连接资源紧张的情况.但在持续的压力下,Jedis连接池内的资源竟然大部分都不可用了,最终测试结果以连接池中所有的资源都不可用而告终.

Jedis连接池竟然会资源泄露吗?

3.排查过程

我们马上紧急dump了堆内存,开始分析为什么连接池所有的资源都不可用了,虽然是压测,但压力还没大到把Redis连接池所有资源都繁忙的才对.所以我们一致猜测,应该是某个地方在使用JedisPool中Jedis后没有释放资源导致的.

但在排查了工具类中所有的函数后,发现并没有发现未释放资源的情况.这和我们预想的问题起因不太一致,只能继续排查堆dump,看有没有别的发现.

果不其然,我们发现了堆中Jedis对象都很奇怪,几乎所有Jedis对象里面的socket连接都是closed的状态.socket都close了,当然这个连接就用不了了.但比较诡异的一个点是明明socket已经close了,但表示连接是否损坏的broken字段却是false,意思是并没有损坏.

Jedis连接池竟然会资源泄露吗?

我又注意到了另一个很诡异的问题,明明是从连接池中取出的资源,资源与连接池绑定的映射字段dataSource却是null.难道资源在使用过程中,有什么操作导致资源和池之间解除了绑定关系么?

Jedis连接池竟然会资源泄露吗?

排查到这里基本就可以确定,这个状态不一致的问题就是导致线程池资源耗尽的元凶了,因为连接池认为资源并没有broken,但socket其实已经closed了,连接池也没办法对这些不可用的资源做回收.但想知道为什么会产生这种情况,还是需要去读Jedis的源码.

4.分析源码

从上面的代码可以看到,从JedisPool中获取资源首先要调用getResource()函数.

然后释放资源的时候调用的是Jedisclose()函数.

如果是正常情况下,获取到资源,操作Jedis,最后归还资源到池中,是不会有问题的.但这里有一个非常不明显的线程安全问题.

Jedis连接池竟然会资源泄露吗?

1.线程1在某个资源刚归还到池中并且还没执行到this.DataSource = null

2.同一资源被线程2从池里面获取出来,并将资源与JedisPool绑定

3.线程1执行到this.DataSource = null,将同一资源解绑

4.线程2使用结束后,释放资源,发现dataSource是null认为资源不是从池里取出来的,关闭了socket.

总结

Jedis团队已经在2.10.2版本将该bug修复.

理论上只要并发量够大并且服务启动时间足够长,这个问题几乎是100%复现的.

所以希望看到的小伙伴关注下自己负责的项目中Jedis的版本,如果看到Jedis的close()函数中有this.DataSource = null;这行代码,要尽快把Jedis版本升级到2.10.2及以上版本.

0

发表评论

邮箱地址不会被公开。