JedisPool异常Jedis链接处理

问题现象

基于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链接发现异常(如连接超时),不对异常连接销毁的话,会有数据缓存问题。

异常流程:

重现问题测试代码:

 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

[email protected]JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T56!=T94

[email protected]JedisConnectionException: java.net.SocketTimeoutException: Read timed out

T717!=T380

[email protected]JedisConnectionException: java.net.SocketTimeoutException: Read timed out

[email protected]edisConnectionException: java.net.SocketTimeoutException: Read timed out

T204!=T787

T474!=T763

T163!=T542

T552!=T60

T604!=T820

T733!=T624

[email protected]JedisConnectionException: java.net.SocketTimeoutException: Read timed out

[email protected]edisConnectionException: java.net.SocketTimeoutException: Read timed out

[email protected]JedisConnectionException: java.net.SocketTimeoutException: Read timed out

[email protected]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,三种方法可以同时使用,但需要在检查性能消耗和功能稳定性之间衡量。

时间: 2024-08-28 11:09:10

JedisPool异常Jedis链接处理的相关文章

redis学习及实践3---Jedis、JedisPool、Jedis分布式实例介绍

一.相关jar包 主要用到的是jedis的核心包,笔者用到的是2.1.0版:另根据"池"的应用等还需要用到相关jar包.下图是笔者建立的简单的jedis测试project图: jar包的文档可参考: http://www.boyunjian.com/javadoc/org.apache.servicemix.bundles/org.apache.servicemix.bundles.jedis/2.1.0_1/_/redis/clients/jedis/JedisShardInfo.h

使用jedisPool管理jedis,使用jedis操作redis

ps:jedis是redis在java中的客户端操作工具 package com.test; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.List; 6 import java.util.Map; 7 8 import org.junit.Before; 9 import org.junit.Test; 10 11 import redis.clients.jedis.Jedis; 1

TCP中异常关闭链接的意义 异常关闭的情况

终止一个连接的正常方式是发送FIN. 在发送缓冲区中 所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失. 但我们有时也有可能发送一个RST报文段而不是F IN来中途关闭一个连接.这称为异常关闭 . 进程关闭socket的默认方式是正常关闭,如果需要异常关闭,利用 SO_LINGER选项来控制. 异常关闭一个连接对应用程序来说有两个优点: (1)丢弃任何待发的已经无意义的 数据,并立即发送RST报文段: (2)RST的接收方利用关闭方式来 区分另一端执行的是异常关闭还是正常关闭.

项目里面加入redis单机版 和集群版的配置

第一步: 如果你是maven项目,你直接配置就可以了,如果不是需要下载这个包 jedis包 <!-- Redis  客户端 -->          <dependency>                <groupId>redis.clients</groupId>                <artifactId>jedis</artifactId>  </dependency> 2.  单机版测试 @Test 

redis性能调优笔记(can not get Resource from jedis pool和jedis connect time out)

对这段时间redis性能调优做一个记录. 1.单进程单线程 redis是单进程单线程实现的,如果你没有特殊的配置,redis内部默认是FIFO排队,即你对redis的访问都是要在redis进行排队,先入先出的串行执行. 之所以能够保持高性能是因为以下3点: 1)内存操作 2)数据结构简单 3)大多数是hash操作 redis基本的命令耗时都是us级别的,所以及时是单进程单线程,也能保证很高的QPS. 2.can not get Resource from jedis pool和jedis con

jedis入门一

一.下载Jedis的依赖包jedis-2.1.0.jar,然后将其添加到classpath下面. 1. 定义连接:Redis暂时不要设置登录密码 Jedis jedis = new Jedis("192.168.142.12"); 2. 进行键值存储: jedis.set("country", "China"); 3. 获取value值: String country = jedis.get("country"); 4. 删除

Jedis源码分析

对于日常开发,Redis由于单线程的并发模型.丰富的数据结构和简单的API,深受广大程序员的喜爱.Redis提供了多种语言的API,像java.c和python等.之前一直都是使用redis,但是没有多redis的API有一个系统的认识.忙里偷闲,撸一下Redis相关的API的实现,由于我是一个java猿,那么我主要学习了一下jedis的源码,来分析一下Redis的读写流程. 一.Jedis项目结构 : 代码是比较简单的,而且很多类也没有那么多的抽象和继承,其实是比较好懂的.commands包里

Redis连接池Lettuce Jedis 区别

Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server. pring boot框架中已经集成了redis,在1.x.x的版本时默认使用的jedis客户端,现在是2.x.x版本默认使用的lettuce客户端. JedisJedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接 LettuceLettuce的连接是基于Netty的,连接实例(Stat

Redis集群的高可用测试(含Jedis客户端的使用)

Redis集群的使用测试(Jedis客户端的使用) 1.  Jedis客户端建议升级到最新版(当前为2.7.3),这样对3.0.x集群有比较好的支持. https://github.com/xetorthio/jedis http://mvnrepository.com/artifact/redis.clients/jedis 2.  直接在Java代码中链接Redis集群: // 数据库链接池配置 JedisPoolConfig config = new JedisPoolConfig();