1. 程式人生 > 資料庫 >解決Redis連線無法正常釋放的問題

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

錯誤資訊:

IllegalStateException: Invalidated object not currently part of this pool

一、問題描述

前些天用多執行緒執行操作測試驗證vanyar-redis連線池,應用是剛重啟的狀態,執行操作是,開啟10個執行緒同時執行10000次操作。

如下:

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

執行操作完畢後發現控制檯輸出9個下面錯誤資訊:

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

該錯誤大致意思是說:不能將redis連線放回池內,放回連線池的物件是無效的物件。在網上查了很多同類錯誤,都說是進行了兩次returnResource釋放連線資源造成的,因為第一次return成功以後,第二次return就會報上面這個錯誤。但是顯然,我翻遍了程式碼並沒有兩次呼叫returnResource。

檢視redis服務端的連線數詳細資訊如下,前9個連線,idle=453,空閒了453秒了,依然沒有釋放,而連線池設定的是空閒60秒就會被釋放,明顯發生異常了。

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

初步懷疑是多執行緒執行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

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

二、報錯原因分析

執行緒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已經不為空了,直接返回。現在多執行緒安全的問題就得以解決。

附上,解決前後對比圖:

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

補充知識: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連線無法正常釋放的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。