一次jedis使用不规范,导致redis客户端close_wait大量增加的bug
2017-03-27 00:00
1071 查看
摘要: Timeout waiting for idle object
最近开发反馈了一个问题,说系统使用了codis之后,发现当并发量上来之后,会抛出异常:could not get resource from pool,更底层的原因是:Timeout waiting for idle object,然后开始查问题。
1、可能是配置问题?
我们对jodis进行了一层浅封装,将配置进行独立,开放给开发人员的配置比较少,也就几个:
没发现什么问题,因为其他系统也是就配置这几个参数,然后调整这些参数,没有任何改善,仍然会出现问题
2、代码分析
RoundRobinJedisPool代码中有如下代码:
(提出问题的同事发现的,感谢)for循环内部的代码不是原子的,尝试添加了同步块,突然发现,问题解决了,此问题不报了,到此,以为解决了(其实这是牵出另外一个问题的关键),修改之后代码如下:
3、系统开始产生大量的close_wait
2小时不到,产生了28000+,而且还在继续上升,持续下去,系统就会因为文件句柄被耗尽而宕机,此问题更紧急。
3.1、close_wait产生的原因
网上有大量的解释管理close_wait和time_wait产生的原因,我就不多讲,自己Google,简单来说就是“被关闭”了,比如这里的问题就是,codis_proxy主动断开了客户端的Jedis连接,而Jedis连接无法感知到此连接被关闭了,此时网络状态就是close_wait了,所以就这方面入手
3.2、codis_proxy中的主动关闭配置?
我们使用的是codis2.0版本,其中proxy有一个配置:session_max_timeout=1800,此配置的含义是:当proxy发现某个客户端连接超过1800s还没有数据发送过来,就主动的关闭该连接。但有一点推测不通:jedis采用的common-pool2来管理Jedis实例,默认设置超过30s就会扫描idle队列,凡是idle时间超过60s的Jedis对象都会被回收,而proxy的设置为1800s,所以没有这种可能,代码如下:
3.3、common-pool2本身的bug?
开始分析common-pool2的代码,代码本身不复杂,可以参考我转载的别人的blog:https://my.oschina.net/u/1178805/blog/867730
关键流程:
borrowObject:当调用Jedis的getResource()方法时候,底层是去调用Pool类的getResource方法:
borrowObject的源码:
以上两段代码就是文初贴出来的两个问题,并发量上来的时候确实有可能出现这个问题。分析如下:
从idleObjects中获取Jedis实例,如果为空则进行创建,如果不为空则返回;当并发调用create方法的时候,且idleObjects一直为空的情况下,就开始报错了:Timeout waiting for idle object,也就产生了我们文初的问题。但什么情况下会导致idleObjects一直为空呢?
3.4、idleObjects一直为空?
由于jedis底层采用common-pool2来进行Jedis实例的管理,而common-pool2的玩法就是:有借有还,再借不难,只借不还,要你好看。通过borrowObject获取对象,通过returnObject归还对象,Jedis也对此做了封装,前面展现了getResource的源码,内部是调用borrowObject,归还是通过close方法执行的:
所以,答案终于找到了,没有调用close方法来进行归还。
4、水落石出
开发没有使用我们封装好的缓存操作代码,而是自己封装了jedis代码来操作:
当没有调用close方法返回Jedis实例的情况下,idleObjects一直是空的,超时是必然的,然后proxy发现大量的连接没有数据进入,开始大批量的关闭连接,客户端close_wait至此开始增加,将这里调用close方法修改之后问题解决
5、经验
5.1、经验证:线上出现的不可思议的问题往往都是非常SB的问题(^_^)
5.2、在没有绝对的把握情况下,不要随意修改经过线上验证的代码
5.3、要清楚的知道技术实现的原理,才能融会贯通,找出问题的关键
最近开发反馈了一个问题,说系统使用了codis之后,发现当并发量上来之后,会抛出异常:could not get resource from pool,更底层的原因是:Timeout waiting for idle object,然后开始查问题。
1、可能是配置问题?
我们对jodis进行了一层浅封装,将配置进行独立,开放给开发人员的配置比较少,也就几个:
codis.pool.maxTotal=1000 //对象池最大数 codis.pool.maxIdle=1000 //idle对列最大数 codis.pool.minIdle=0 //idle队列最小数 codis.pool.maxWaitMillis=20000 //获取连接超时时间
没发现什么问题,因为其他系统也是就配置这几个参数,然后调整这些参数,没有任何改善,仍然会出现问题
2、代码分析
RoundRobinJedisPool代码中有如下代码:
public Jedis getResource() { ImmutableList<PooledObject> pools = this.pools; if (pools.isEmpty()) { throw new JedisException("Proxy list empty"); } for (;;) { int current = nextIdx.get();//1.获取上次使用哪个proxy int next = current >= pools.size() - 1 ? 0 : current + 1;//2.轮询到另外一个proxy if (nextIdx.compareAndSet(current, next)) {// 3.设置本次使用的proxy,以供下次使用 return pools.get(next).pool.getResource();//获取该proxy的JedisPool并获取Jedis实例 } } }
(提出问题的同事发现的,感谢)for循环内部的代码不是原子的,尝试添加了同步块,突然发现,问题解决了,此问题不报了,到此,以为解决了(其实这是牵出另外一个问题的关键),修改之后代码如下:
public Jedis getResource() { ImmutableList<PooledObject> pools = this.pools; if (pools.isEmpty()) { throw new JedisException("Proxy list empty"); } /** * 增加同步,防止高并发下的could not get resource from pool的异常 */ synchronized (this.pools) { for (;;) { int current = nextIdx.get(); int next = current >= pools.size() - 1 ? 0 : current + 1; if (nextIdx.compareAndSet(current, next)) { return pools.get(next).pool.getResource(); } } } }
3、系统开始产生大量的close_wait
2小时不到,产生了28000+,而且还在继续上升,持续下去,系统就会因为文件句柄被耗尽而宕机,此问题更紧急。
3.1、close_wait产生的原因
网上有大量的解释管理close_wait和time_wait产生的原因,我就不多讲,自己Google,简单来说就是“被关闭”了,比如这里的问题就是,codis_proxy主动断开了客户端的Jedis连接,而Jedis连接无法感知到此连接被关闭了,此时网络状态就是close_wait了,所以就这方面入手
3.2、codis_proxy中的主动关闭配置?
我们使用的是codis2.0版本,其中proxy有一个配置:session_max_timeout=1800,此配置的含义是:当proxy发现某个客户端连接超过1800s还没有数据发送过来,就主动的关闭该连接。但有一点推测不通:jedis采用的common-pool2来管理Jedis实例,默认设置超过30s就会扫描idle队列,凡是idle时间超过60s的Jedis对象都会被回收,而proxy的设置为1800s,所以没有这种可能,代码如下:
public class JedisPoolConfig extends GenericObjectPoolConfig { public JedisPoolConfig() { // defaults to make your life with connection pool easier :) setTestWhileIdle(true); setMinEvictableIdleTimeMillis(60000);//对象存活时间 setTimeBetweenEvictionRunsMillis(30000);//清除对象的线程执行间隔 setNumTestsPerEvictionRun(-1);//每次扫描会扫描多少个对象,-1为不限制 } }
3.3、common-pool2本身的bug?
开始分析common-pool2的代码,代码本身不复杂,可以参考我转载的别人的blog:https://my.oschina.net/u/1178805/blog/867730
关键流程:
borrowObject:当调用Jedis的getResource()方法时候,底层是去调用Pool类的getResource方法:
public T getResource() { try { return internalPool.borrowObject(); } catch (Exception e) { throw new JedisConnectionException("Could not get a resource from the pool", e); } } internalPool:为GenericObjectPool类型的成员变量,此类就是common-pool2的对象池
borrowObject的源码:
public T borrowObject(long borrowMaxWaitMillis) throws Exception { //忽略了部分和本文无关的代码 PooledObject<T> p = null; // Get local copy of current config so it is consistent for entire // method execution boolean blockWhenExhausted = getBlockWhenExhausted(); boolean create; long waitTime = System.currentTimeMillis(); while (p == null) { create = false; if (blockWhenExhausted) {//blockWhenExhausted的默认配置为true p = idleObjects.pollFirst(); if (p == null) { p = create();//这里并发情况下有可能返回为null if (p != null) { create = true; } } if (p == null) { if (borrowMaxWaitMillis < 0) {//我们配置的为20000ms p = idleObjects.takeFirst(); } else { p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); } } if (p == null) {//这里就是我们文初贴出来的错误信息 throw new NoSuchElementException( "Timeout waiting for idle object"); } } } //忽略了部分和本文无关的代码 return p.getObject(); }
以上两段代码就是文初贴出来的两个问题,并发量上来的时候确实有可能出现这个问题。分析如下:
从idleObjects中获取Jedis实例,如果为空则进行创建,如果不为空则返回;当并发调用create方法的时候,且idleObjects一直为空的情况下,就开始报错了:Timeout waiting for idle object,也就产生了我们文初的问题。但什么情况下会导致idleObjects一直为空呢?
3.4、idleObjects一直为空?
由于jedis底层采用common-pool2来进行Jedis实例的管理,而common-pool2的玩法就是:有借有还,再借不难,只借不还,要你好看。通过borrowObject获取对象,通过returnObject归还对象,Jedis也对此做了封装,前面展现了getResource的源码,内部是调用borrowObject,归还是通过close方法执行的:
public void close() { if (dataSource != null) { if (client.isBroken()) { this.dataSource.returnBrokenResource(this);//内部调用invalidObject方法 } else { this.dataSource.returnResource(this);//内部调用returnObject方法 } } else { client.close(); } }
所以,答案终于找到了,没有调用close方法来进行归还。
4、水落石出
开发没有使用我们封装好的缓存操作代码,而是自己封装了jedis代码来操作:
public synchronized Jedis getJedis () { return jedisPool.getResource();//就是这里,只借不还,没有调用close方法 }
当没有调用close方法返回Jedis实例的情况下,idleObjects一直是空的,超时是必然的,然后proxy发现大量的连接没有数据进入,开始大批量的关闭连接,客户端close_wait至此开始增加,将这里调用close方法修改之后问题解决
5、经验
5.1、经验证:线上出现的不可思议的问题往往都是非常SB的问题(^_^)
5.2、在没有绝对的把握情况下,不要随意修改经过线上验证的代码
5.3、要清楚的知道技术实现的原理,才能融会贯通,找出问题的关键
相关文章推荐
- Redis 客户端Jedis使用(一)
- Redis的Java客户端jedis-2.4.2.jar使用
- 正确使用HttpClient,避免出现大量CLOSE_WAIT的TCP链接
- Redis 客户端Jedis使用(一)
- 关于流量升高导致TIME_WAIT增加,MySQL连接大量失败的问题
- 在cxf中使用配置避免增加字段导致客户端必须更新的问题
- 一次服务端大量CLOSE_WAIT问题的解决
- Redis服务器搭建/配置/及Jedis客户端的使用方法
- 使用Redis的Java客户端Jedis
- redis客户端jedis的简单使用
- Redis服务器搭建/配置/及Jedis客户端的使用方法
- Redis 客户端Jedis使用(一)
- redis客户端jedis中的小bug
- Redis的java客户端(jedis)配置(spring)与使用
- Redis客户端——Jedis使用
- CXF WebService 8 - 在cxf中使用配置避免增加字段导致客户端必须更新、同步实体属性的问题
- redis的Java客户端jedis使用示例
- 关于流量升高导致TIME_WAIT增加,MySQL连接大量失败的问题
- 【Redis】redis的安装、配置运行及Jedis客户端的开发使用