JedisPool异常Jedis连接处理

 

问题现象(jedis-2.1.0.jar)java

 

基于JedisPool管理Jedis对象,经过get方法获取值,出现key对应的value值错误,例如:redis

K Vapache

a a缓存

b b服务器

Jedis.get(“a”)==’b’;网络

经过获取key为a的值,但获取了值b来。并发

同一套代码的项目,分别部署在两个不一样的应用集群,其中一个集群出现这种问题,而另外一个集群却没有出现。dom

 

问题分析源码分析

 

经过表象能够看出,应该是连接池的Jedis对象连接出现错乱而致使的。而两个集群中的其中一个集群出现,这两个集群的惟一区别就是网络环境不同,因此链接Redis服务器的网络是有差异的。性能

 

问题思考

 

根据以上信息,能够大体判断出应该跟网速和JedisPool连接池的超时时间(500毫秒)设置有关。那接下来的问题是,若是网络差的集群,出现redis链接超时,那么Jedis为何会错误呢?是否在链接池的配置不当知道呢?

 

问题发现

带着以上的疑问,继续Google和百度相关资料,结果发现returnBrokenResource这个方法。经过资料查找和对JedisPool的源码分析,此方法是销毁异常Jedis链接的。若是Jedis连接发现异常(如链接超时),不对异常链接销毁的话,会有数据缓存问题。

异常流程:

重现问题测试代码(设置1ms的readTimeOut时间,以便问题重现):

 1 package test;  2 
 3 import java.io.IOException;  4 import java.io.InputStream;  5 import java.util.Properties;  6 import java.util.Random;  7 import org.apache.commons.lang.StringUtils;  8 import redis.clients.jedis.Jedis;  9 import redis.clients.jedis.JedisPool; 10 import redis.clients.jedis.JedisPoolConfig; 11 
12 public class RedisTest implements Runnable{ 13     
14     public static JedisPool pool = null; 15     
16     static { 17          try { 18                     JedisPoolConfig config = new JedisPoolConfig(); 19                     config.setMaxActive(100); 20                     config.setMaxIdle(10); 21                     config.setMaxWait(1000); 22                     config.setTestOnBorrow(false); 23                     config.setTestOnReturn(false); 24                     config.setTestWhileIdle(true); 25                     config.setTimeBetweenEvictionRunsMillis(30000); 26                     config.setNumTestsPerEvictionRun(10); 27                     config.setMinEvictableIdleTimeMillis(60000); 28             pool = new JedisPool(config, "192.168.22.213", 6379,1); 29         } catch (Exception e) { 30             System.out.println("【jedispool init error】"); 31  } 32  } 33 
34 public void run() { 35         
36         Jedis jedis = null; 37         String result = ""; 38         int i = new Random().nextInt(1000); 39         
40         try{ 41             jedis=pool.getResource(); 42             result = jedis.get("T"+i); 43             
44             if(StringUtils.isNotEmpty(result) && !result.equals("T"+i)){ 45                 System.out.println(result+"!=T"+i); 46  } 47             
48         }catch(Exception e){ 49             System.out.println(jedis+e.toString()); 50             
51         }finally{ 52             if(jedis!=null){ 53  pool.returnResource(jedis); 54  } 55  } 56         
57  } 58     
59     /**
60  * 模拟2000线程并发 61      */
62     public static void main(String[] args) throws Exception { 63         
64         for(int i=0;i<2000;i++){ 65             new Thread(new RedisTest()).start(); 66  } 67  } 68 }

执行结果:

……

T50!=T47

redis.clients.jedis.Jedis@1295fe8redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T56!=T94

redis.clients.jedis.Jedis@151b0a5redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T717!=T380

redis.clients.jedis.Jedis@131303fredis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

redis.clients.jedis.Jedis@602b6bredis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T204!=T787

T474!=T763

T163!=T542

T552!=T60

T604!=T820

T733!=T624

redis.clients.jedis.Jedis@131303fredis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

redis.clients.jedis.Jedis@d56b37redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

redis.clients.jedis.Jedis@151b0a5redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

redis.clients.jedis.Jedis@1295fe8redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T784!=T948

T440!=T672

T97!=T867

……

以上结果出现许多键值不对应的状况。

 

解决方案

 

当Jedis读超时时,把此实例销毁,以避免形成后续伤害。

销毁异常Jedis有三种方法:

方法1:加入红色代码,当读取Redis数据时任何异常都抛弃此Jedis实例

try{ jedis=pool.getResource(); result = jedis.get("T"+i); if(StringUtils.isNotEmpty(result) && !result.equals("T"+i)){ System.out.println(result+"!=T"+i); } }catch(Exception e){ System.out.println(jedis+e.toString()); if(jedis!=null){ pool.returnBrokenResource(jedis); } }finally{ if(jedis!=null){ pool.returnResource(jedis); } }

方法2:配置JedisPool的TestOnBorrow为true

config.setTestOnBorrow(true);

 

方法3:配置JedisPool的TestOnReturn为true

config.setTestOnReturn(true);

 

总结

其实以上三种方法原理都是同样,就是检查Jedis的有效性,销毁异常Jedis连接实例。只是检查的时间不同。

而致使量应用集群中其中之一出现,能够定位为有问题集群到Redis集群服务器的网速比正常集群的差(500ms超时限制)

 

方法1是在发生时检验销毁;

 

方法2是在从链接池获取Jedis实例时检查;

截图源码来自package org.apache.commons.pool.impl.GenericObjectPool

 

方法3是在归还Jedis实例给链接池时检查;

截图源码来自package org.apache.commons.pool.impl.GenericObjectPool

 

以上三种检查链接有效性方法都是一致:

boolean isNormal = false;

try{

                   isNormal = (jedis.isConnected()) && (jedis.ping().equals("PONG"));

}catch(Exception e){

                   isNormal = false;

}

 

注1,三种方法能够同时使用,但须要在检查性能消耗和功能稳定性之间衡量。

 

文章更新时间:2016-07-01

更新内容:当我把jedis更新到2.8.1的时候使用returnBrokenResource和returnResource显示方法过时,缘由是Jedis类重写了这个close方法,本来是没有的,它在close已经帮你判断好,源码以下所示:

  public void close()
  {
    if (this.dataSource != null) {
      if (this.client.isBroken())
        this.dataSource.returnBrokenResource(this);
      else
        this.dataSource.returnResource(this);
    }
    else
      this.client.close();
  }
相关文章
相关标签/搜索