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();
  }
相關文章
相關標籤/搜索