解决Redis连接无法正常释放的问题

错误信息:

IllegalStateException: Invalidated object not currently part of this pool

一、问题描述

前些天用多线程执行操作测试验证vanyar-redis连接池,应用是刚重启的状态,执行操作是,开启10个线程同时执行10000次操作。

如下:

执行操作完毕后发现控制台输出9个下面错误信息:

该错误大致意思是说:不能将redis连接放回池内,放回连接池的对象是无效的对象。在网上查了很多同类错误,都说是进行了两次returnResource释放连接资源造成的,因为第一次return成功以后,第二次return就会报上面这个错误。但是显然,我翻遍了代码并没有两次调用returnResource。

查看redis服务端的连接数详细信息如下,前9个连接,idle=453,空闲了453秒了,依然没有释放,而连接池设置的是空闲60秒就会被释放,明显发生异常了。

初步怀疑是多线程执行redis操作,初始化redis连接池有问题。于是重启应用,先执行单线程redis操作,再执行多线程redis操作,没有发生上面的问题。redis服务端连接均能正常释放。由此得出结论,当线程池在未初始化的时候,由于多线程同时执行redis连接池初始化工作引起的问题。

看代码(RedisJedisPool未优化之前):当10个线程同时请求redis连接资源时,10个线程都发现连接池为空(因为创建连接池相比创建线程比较耗时),这时10个线程都各自初始化成功一个连接池,并从中取得redis连接,并执行了redis操作。执行完毕,returnResource的时候,由于此时pool变量的引用是最后一个线程初始化的连接池,前面9个线程获得的redis连接并不属于最后一个连接池的资源,所以抛错:IllegalStateException: Invalidated object not currently part of this pool

二、报错原因分析

线程1 : 创建redis连接池1 : 获得redis连接1

线程2 : 创建redis连接池2 : 获得redis连接2

线程3 : 创建redis连接池3 : 获得redis连接3

……

线程8 : 创建redis连接池8 : 获得redis连接8

线程9 : 创建redis连接池9 : 获得redis连接9

线程10 : 创建redis连接池10 : 获得redis连接10

全局变量pool引用 指向 redis连接池10

当线程1-9 把redis连接1-9 归还给pool-redis连接池10

reds连接池10自然就报错,说:

IllegalStateException: Invalidated object not currently part of this pool

三、解决办法

由于创建线程池,连接池等工作都是相对比较耗时的,所以我们一般放在应用启动的时候就初始化,把连接池的初始化工作交给Spring容器管理,同时把初始化连接池和获取连接两个操作实现方法分离,对初始化连接池的方法加上同步锁机制,并且二次判断是否为空,就算多线程情况下,在二次判断是否为空的时候,pool已经不为空了,直接返回。现在多线程安全的问题就得以解决。

附上,解决前后对比图:

补充知识:java spring框架中方法级redis的连接自动获取和释放实现

java中使用redis总是需要处理redis连接的获取,释放等操作,每次使用都会使代码变的特别丑陋,模仿spring中aop的实现,用动态代理写一个 连接自动获取和释放的工具

主要思路

JedisManageSupport 抽象类 类似于 aop的切入点,所有继承了该类(一般都是service层)的类,可以使用提供的获取redis的方法获取redis,并且不需要释放

JedisBeanPostProcessor 继承BeanPostProcessor ,会在bean初始化时执行自己定义的逻辑:

如果A类继承了 JedisManageSupport ,就会获取redis连接并且放到JedisManageSupport 的成员变量里,A类的实例(其实是cglib动态代理生成的

A类的子类的实例)就可以使用该redis连接 进行相关操作了

代理类的实例见源码

源码如下

public class JedisBeanPostProcessor implements BeanPostProcessor {

@Autowired
ShardedJedisPool shardedJedisPool;

static final Logger logger = Logger.getLogger(JedisBeanPostProcessor.class);

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JedisManageSupport) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(new JedisInterceptor(shardedJedisPool, bean));
Object targetBean = enhancer.create();
return targetBean;
}
else {
return bean;
}
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

class JedisInterceptor implements MethodInterceptor {

static final Logger logger = Logger.getLogger(JedisInterceptor.class);

ShardedJedisPool pool;

Object src;

public JedisInterceptor(ShardedJedisPool pool, Object src) {
this.pool = pool;
this.src = src;
}

@Override
public Object intercept(Object target, Method method, Object[] arguments, MethodProxy methodProxy) throws Throwable {
Object result = null;
if (target instanceof JedisManageSupport) {
if (this.isDeclaredMethod(target, method)) {
ShardedJedis jedis = null;
try {
JedisManageSupport support = (JedisManageSupport) src;
jedis = pool.getResource();
support.setShardedJedis(jedis);
// logger.debug("调用之前注入jedis对象,method:" + method);
/**
* 下面代码可以使用 method.invoke(src,arguments)。 不能使用
* methodProxy.invokeSuper(target,arguments);
* 因为A类中用Autowired注入的属性,生成代理的子类B后,因为子类B是新建的类。从父类继承的属性没有被初始化,
* 使用methodProxy.invokeSuper()执行是,会报空指针异常.
*/
result = methodProxy.invoke(src, arguments);
support.setShardedJedis(null);
}
catch (Exception e) {
pool.returnBrokenResource(jedis);
e.printStackTrace();
}
finally {
if (jedis != null) {
pool.returnResource(jedis);
}
// logger.debug("调用之后归还jedis对象,method:" + method);
}
}
else {
result = methodProxy.invoke(src, arguments);
}
}
else {
throw new Exception("使用该代理必须继承JedisManageSupport");
}
return result;
}

/**
* 是否是target类本身定义的非私有的方法,还是继承的父类
* @return true是target自己类的并且不是私有的的,
*/
private boolean isDeclaredMethod(Object target, Method arg1) {
Method temp = null;
try {
temp = target.getClass().getDeclaredMethod(arg1.getName(), arg1.getParameterTypes());
}
catch (SecurityException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
/**
* 不为null,并且是非私有的,返回true
*/
if (temp != null) {

return true;
}
else {
return false;
}
}
}

public abstract class JedisManageSupport {
ThreadLocal<ShardedJedis> jedisHolder = new ThreadLocal<ShardedJedis>();

public final ShardedJedis getShardedJedis() {
return jedisHolder.get();
}

public final void setShardedJedis(ShardedJedis jedis) {
jedisHolder.set(jedis);
}

/**
* 如果某个键不同单位之间也不会重复,可以使用这个方法生成redis的键
*/
public final byte[] assemKey(String baseKey) {
Assert.isTrue(StringUtils.isNotBlank(baseKey), "参数不能为空");
return baseKey.getBytes();
}

/**
* 根据tableName+prefix 构造唯一key与assemKey(String baseKey, String tableName)
* 规则一致
*/
public final byte[] assemKeyByPrefix(String tableName, String baseKey) {
Assert.isTrue(StringUtils.isNotBlank(baseKey), "参数不能为空");
Assert.isTrue(StringUtils.isNotBlank(tableName), "参数不能为空");
UnitInfo unit = WebService.getUnitInfo();
Assert.isTrue(unit != null, "单位信息获取不到");
return (tableName + "-" + unit.getPrefix() + "-" + baseKey).getBytes();
}

/**
*
* 不同前缀的表中可能有相同的键,同一个表中也可能是有重复的baseKey时,用这个生成redis的key 比如 用户信息表的
* username字段,不同的用户信息表允许重复的username,mooc_t_userinfo
* 也允许有相同的账号,所以生成redis的key时,需要用到单位的mooc_school 放入redis中
*/
public final byte[] assemKeyByFid(String tableName, String baseKey) {
UnitInfo unit = WebService.getUnitInfo();
Assert.isTrue(unit != null, "单位信息获取不到");
return (tableName + "-" + unit.getMoocSchool() + "-" + baseKey).getBytes();
}

}

以上这篇解决Redis连接无法正常释放的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java连接redis及基本操作示例

    本文实例讲述了Java连接redis及基本操作.分享给大家供大家参考,具体如下: 点击此处:本站下载安装. 解压安装 启动redis:使用cd命令切换目录到 D:\redis运行redis-server.exe redis.windows.conf 默认端口为6379 访问:切换到redis目录下运行 redis-cli.exe -h 127.0.0.1 -p 6379. pom.xml: <project xmlns="http://maven.apache.org/POM/4.0.0&

  • Redis分布式锁python-redis-lock使用方法

    python-redis-lock 多个redis客户端访问同一个redis服务端,控制并发. github:https://pypi.org/project/python-redis-lock/ 在使用这个库之前,需要安装如下: pip install python-redis-lock 使用锁的示例: lock = redis_lock.Lock(conn, "name-of-the-lock") if lock.acquire(blocking=False): print(&qu

  • 基于SpringBoot2.0默认使用Redis连接池的配置操作

    SpringBoot2.0默认采用Lettuce客户端来连接Redis服务端的 默认是不使用连接池的,只有配置 redis.lettuce.pool下的属性的时候才可以使用到redis连接池 redis: cluster: nodes: ${redis.host.cluster} password: ${redis.password} lettuce: shutdown-timeout: 100 # 关闭超时时间 pool: max-active: 8 # 连接池最大连接数(使用负值表示没有限制

  • 解决Redis连接无法正常释放的问题

    错误信息: IllegalStateException: Invalidated object not currently part of this pool 一.问题描述 前些天用多线程执行操作测试验证vanyar-redis连接池,应用是刚重启的状态,执行操作是,开启10个线程同时执行10000次操作. 如下: 执行操作完毕后发现控制台输出9个下面错误信息: 该错误大致意思是说:不能将redis连接放回池内,放回连接池的对象是无效的对象.在网上查了很多同类错误,都说是进行了两次returnR

  • Redisson如何解决Redis分布式锁提前释放问题

    目录 前言: 一.问题描述: 二.原因分析: 三.解决方案: 1.思考: 2.Redisson简单配置: 3.使用样例: 四.源码分析 1.lock加锁操作 2.unlock解锁操作 总结: 相关参考: 前言: 在分布式场景下,相信你或多或少需要使用分布式锁来访问临界资源,或者控制耗时操作的并发性. 当然,实现分布式锁的方案也比较多,比如数据库.redis.zk 等等.本文主要结合一个线上案例,讲解 redis 分布式锁的相关实现. 一.问题描述: 某天线上出现了数据重复处理问题,经排查后发现,

  • redis scan命令导致redis连接耗尽,线程上锁的解决

    使用redis scan方法无法获取connection,导致线程锁死. 0.关键字 redis springboot redistemplate scan try-with-resource 1.异常现象 应用部署后,功能正常使用,但约数小时左右,部分功能接口异常,接口请求无响应. 2.异常排查 查看堆栈信息,jstask pid.首先找到java进程pid:输出堆栈信息至log文件,jstask 30 > stask.log,看到与redis相关的日志,线程状态为waiting. "p

  • springboot2整合redis使用lettuce连接池的方法(解决lettuce连接池无效问题)

    lettuce客户端 Lettuce 和 Jedis 的都是连接Redis Server的客户端程序.Jedis在实现上是直连redis server,多线程环境下非线程安全(即多个线程对一个连接实例操作,是线程不安全的),除非使用连接池,为每个Jedis实例增加物理连接.Lettuce基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问(即多个线程公用一个连接实例,线程安全),同时它是可伸缩的设计,一个连接

  • Redis连接错误的情况总结分析

    前言 最近由于流量增大,redis 出现了一连串错误,比如: LOADING Redis is loading the dataset in memory use of closed network connection connection pool exhausted connection refuse by peer 一个个来分析. LOADING Redis is loading the dataset in memory 这里至少有2种可能 可用内存太小,修改 redis.conf 中

  • 一次Docker中Redis连接暴增的问题排查实战记录

    周六生产服务器出现redis服务器不可用状态,错误信息为: 状态不可用,等待后台检查程序恢复方可使用.Unexpected end of stream; expected type 'Status' 如下图所示,下图6300就是我们redis服务器运行的端口. 头一次碰到此类问题,心想难道是redis挂掉了,随即通过telnet ip+端口.发现运行正常,然后就想着进入redis看下目前连接情况.一看发现竟然高达1903条这么多. 然后想着应该是代码创建redis连接过多导致的,查看代码. 发现

  • 解决redis与Python交互取出来的是bytes类型的问题

    基本代码 from redis import * if __name__ == '__main__': sr = StrictRedis(host='localhost', port=6379, db=0) result=sr.set('name','python') print(result) result1 = sr.get('name') print(result1) 运行结果: True b'python' 这里我们存进去的是字符串类型的数据,取出来却是字节类型的,这是由于python3

  • Redis连接池配置及初始化实现

    加入db选择后的redis连接池配置代码 public class RedisPoolConfigure { //Redis服务器IP private String ADDR ; //Redis的端口号 private int PORT ; //可用连接实例的最大数目 private int MAX_ACTIVE ; //pool中的idle jedis实例数 private int MAX_IDLE ; //等待可用连接的最大时间,单位毫秒 private int MAX_WAIT ; //超

  • Go实现Redis连接池方法

    目录 一.什么是连接池,连接池有什么用 二.代码展示 一.什么是连接池,连接池有什么用 先看看别人是怎么介绍连接池的吧: 连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象.使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用.而连接的建立.断开都由连接池自身来管理.同时,还可以通过设置连接池的参数来控制连接池中的初始连接数.连接的上下限数以及每个连接的最大使

随机推荐