• JedisPool异常Jedis链接处理


     

    问题现象(jedis-2.1.0.jar)

     

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

    K V

    a a

    b b

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

    通过获取key为a的值,但获取了值b来。

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

     

    问题分析

     

    通过表象可以看出,应该是链接池的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();
      }
  • 相关阅读:
    阿里云 redis 通过rinetd 进行端口透传
    Linux文件系统的实现
    门外汉的IT
    被解放的姜戈05 黑面管家
    被解放的姜戈04 各取所需
    被解放的姜戈03 所谓伊人
    统计Go, Go, Go
    被解放的姜戈02 庄园疑云
    被解放的姜戈01 初试天涯
    亚马逊云架设WordPress博客
  • 原文地址:https://www.cnblogs.com/wcd144140/p/4883139.html
Copyright © 2020-2023  润新知