主頁 > 知識庫 > 解決Redis連接無法正常釋放的問題

解決Redis連接無法正常釋放的問題

熱門標簽:日本中國地圖標注 山東外呼銷售系統(tǒng)招商 超呼電話機器人 宿遷便宜外呼系統(tǒng)平臺 魔獸2青云地圖標注 鄭州人工智能電銷機器人系統(tǒng) 十堰營銷電銷機器人哪家便宜 貴州電銷卡外呼系統(tǒng) 北京400電話辦理收費標準

錯誤信息:

IllegalStateException: Invalidated object not currently part of this pool

一、問題描述

前些天用多線程執(zhí)行操作測試驗證vanyar-redis連接池,應用是剛重啟的狀態(tài),執(zhí)行操作是,開啟10個線程同時執(zhí)行10000次操作。

如下:

執(zhí)行操作完畢后發(fā)現(xiàn)控制臺輸出9個下面錯誤信息:

該錯誤大致意思是說:不能將redis連接放回池內(nèi),放回連接池的對象是無效的對象。在網(wǎng)上查了很多同類錯誤,都說是進行了兩次returnResource釋放連接資源造成的,因為第一次return成功以后,第二次return就會報上面這個錯誤。但是顯然,我翻遍了代碼并沒有兩次調(diào)用returnResource。

查看redis服務端的連接數(shù)詳細信息如下,前9個連接,idle=453,空閑了453秒了,依然沒有釋放,而連接池設置的是空閑60秒就會被釋放,明顯發(fā)生異常了。

初步懷疑是多線程執(zhí)行redis操作,初始化redis連接池有問題。于是重啟應用,先執(zhí)行單線程redis操作,再執(zhí)行多線程redis操作,沒有發(fā)生上面的問題。redis服務端連接均能正常釋放。由此得出結(jié)論,當線程池在未初始化的時候,由于多線程同時執(zhí)行redis連接池初始化工作引起的問題。

看代碼(RedisJedisPool未優(yōu)化之前):當10個線程同時請求redis連接資源時,10個線程都發(fā)現(xiàn)連接池為空(因為創(chuàng)建連接池相比創(chuàng)建線程比較耗時),這時10個線程都各自初始化成功一個連接池,并從中取得redis連接,并執(zhí)行了redis操作。執(zhí)行完畢,returnResource的時候,由于此時pool變量的引用是最后一個線程初始化的連接池,前面9個線程獲得的redis連接并不屬于最后一個連接池的資源,所以拋錯:IllegalStateException: Invalidated object not currently part of this pool

二、報錯原因分析

線程1 : 創(chuàng)建redis連接池1 : 獲得redis連接1

線程2 : 創(chuàng)建redis連接池2 : 獲得redis連接2

線程3 : 創(chuàng)建redis連接池3 : 獲得redis連接3

……

線程8 : 創(chuàng)建redis連接池8 : 獲得redis連接8

線程9 : 創(chuàng)建redis連接池9 : 獲得redis連接9

線程10 : 創(chuàng)建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

三、解決辦法

由于創(chuàng)建線程池,連接池等工作都是相對比較耗時的,所以我們一般放在應用啟動的時候就初始化,把連接池的初始化工作交給Spring容器管理,同時把初始化連接池和獲取連接兩個操作實現(xiàn)方法分離,對初始化連接池的方法加上同步鎖機制,并且二次判斷是否為空,就算多線程情況下,在二次判斷是否為空的時候,pool已經(jīng)不為空了,直接返回?,F(xiàn)在多線程安全的問題就得以解決。

附上,解決前后對比圖:

補充知識:java spring框架中方法級redis的連接自動獲取和釋放實現(xiàn)

java中使用redis總是需要處理redis連接的獲取,釋放等操作,每次使用都會使代碼變的特別丑陋,模仿spring中aop的實現(xiàn),用動態(tài)代理寫一個 連接自動獲取和釋放的工具

主要思路

JedisManageSupport 抽象類 類似于 aop的切入點,所有繼承了該類(一般都是service層)的類,可以使用提供的獲取redis的方法獲取redis,并且不需要釋放

JedisBeanPostProcessor 繼承BeanPostProcessor ,會在bean初始化時執(zhí)行自己定義的邏輯:

如果A類繼承了 JedisManageSupport ,就會獲取redis連接并且放到JedisManageSupport 的成員變量里,A類的實例(其實是cglib動態(tài)代理生成的

A類的子類的實例)就可以使用該redis連接 進行相關(guān)操作了

代理類的實例見源碼

源碼如下

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("調(diào)用之前注入jedis對象,method:" + method);
/**
* 下面代碼可以使用 method.invoke(src,arguments)。 不能使用
* methodProxy.invokeSuper(target,arguments);
* 因為A類中用Autowired注入的屬性,生成代理的子類B后,因為子類B是新建的類。從父類繼承的屬性沒有被初始化,
* 使用methodProxy.invokeSuper()執(zhí)行是,會報空指針異常.
*/
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("調(diào)用之后歸還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 {
ThreadLocalShardedJedis> jedisHolder = new ThreadLocalShardedJedis>();

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), "參數(shù)不能為空");
return baseKey.getBytes();
}

/**
* 根據(jù)tableName+prefix 構(gòu)造唯一key與assemKey(String baseKey, String tableName)
* 規(guī)則一致
*/
public final byte[] assemKeyByPrefix(String tableName, String baseKey) {
Assert.isTrue(StringUtils.isNotBlank(baseKey), "參數(shù)不能為空");
Assert.isTrue(StringUtils.isNotBlank(tableName), "參數(shù)不能為空");
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連接無法正常釋放的問題就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • 基于SpringBoot2.0默認使用Redis連接池的配置操作
  • Java連接redis及基本操作示例
  • Redis分布式鎖python-redis-lock使用方法

標簽:吉安 北京 大慶 江蘇 臺州 朝陽 楊凌 果洛

巨人網(wǎng)絡通訊聲明:本文標題《解決Redis連接無法正常釋放的問題》,本文關(guān)鍵詞  解決,Redis,連接,無法,正常,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡,涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《解決Redis連接無法正常釋放的問題》相關(guān)的同類信息!
  • 本頁收集關(guān)于解決Redis連接無法正常釋放的問題的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章