佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > 解決Redis連接無(wú)法正常釋放的問(wèn)題

解決Redis連接無(wú)法正常釋放的問(wèn)題

熱門(mén)標(biāo)簽:地方門(mén)戶(hù)網(wǎng)站 服務(wù)外包 Linux服務(wù)器 鐵路電話(huà)系統(tǒng) AI電銷(xiāo) 呼叫中心市場(chǎng)需求 網(wǎng)站排名優(yōu)化 百度競(jìng)價(jià)排名

錯(cuò)誤信息:

IllegalStateException: Invalidated object not currently part of this pool

一、問(wèn)題描述

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

如下:

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

該錯(cuò)誤大致意思是說(shuō):不能將redis連接放回池內(nèi),放回連接池的對(duì)象是無(wú)效的對(duì)象。在網(wǎng)上查了很多同類(lèi)錯(cuò)誤,都說(shuō)是進(jìn)行了兩次returnResource釋放連接資源造成的,因?yàn)榈谝淮蝦eturn成功以后,第二次return就會(huì)報(bào)上面這個(gè)錯(cuò)誤。但是顯然,我翻遍了代碼并沒(méi)有兩次調(diào)用returnResource。

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

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

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

二、報(bào)錯(cuò)原因分析

線程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

當(dāng)線程1-9 把redis連接1-9 歸還給pool-redis連接池10

reds連接池10自然就報(bào)錯(cuò),說(shuō):

IllegalStateException: Invalidated object not currently part of this pool

三、解決辦法

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

附上,解決前后對(duì)比圖:

補(bǔ)充知識(shí):java spring框架中方法級(jí)redis的連接自動(dòng)獲取和釋放實(shí)現(xiàn)

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

主要思路

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

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

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

A類(lèi)的子類(lèi)的實(shí)例)就可以使用該redis連接 進(jìn)行相關(guān)操作了

代理類(lèi)的實(shí)例見(jià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對(duì)象,method:" + method);
/**
* 下面代碼可以使用 method.invoke(src,arguments)。 不能使用
* methodProxy.invokeSuper(target,arguments);
* 因?yàn)锳類(lèi)中用Autowired注入的屬性,生成代理的子類(lèi)B后,因?yàn)樽宇?lèi)B是新建的類(lèi)。從父類(lèi)繼承的屬性沒(méi)有被初始化,
* 使用methodProxy.invokeSuper()執(zhí)行是,會(huì)報(bào)空指針異常.
*/
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對(duì)象,method:" + method);
}
}
else {
result = methodProxy.invoke(src, arguments);
}
}
else {
throw new Exception("使用該代理必須繼承JedisManageSupport");
}
return result;
}

/**
* 是否是target類(lèi)本身定義的非私有的方法,還是繼承的父類(lèi)
* @return true是target自己類(lèi)的并且不是私有的的,
*/
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);
}

/**
* 如果某個(gè)鍵不同單位之間也不會(huì)重復(fù),可以使用這個(gè)方法生成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();
}

/**
*
* 不同前綴的表中可能有相同的鍵,同一個(gè)表中也可能是有重復(fù)的baseKey時(shí),用這個(gè)生成redis的key 比如 用戶(hù)信息表的
* username字段,不同的用戶(hù)信息表允許重復(fù)的username,mooc_t_userinfo
* 也允許有相同的賬號(hào),所以生成redis的key時(shí),需要用到單位的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連接無(wú)法正常釋放的問(wèn)題就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

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

標(biāo)簽:崇左 仙桃 黃山 衡水 湖南 湘潭 銅川 蘭州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《解決Redis連接無(wú)法正常釋放的問(wèn)題》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話(huà)咨詢(xún)

    • 400-1100-266
    延长县| 河南省| 镇远县| 九龙城区| 临城县| 江城| 花垣县| 舞钢市| 大同县| 抚顺县| 城市| 吉安县| 沁源县| 浠水县| 堆龙德庆县| 丹巴县| 台安县| 定远县| 获嘉县| 惠东县| 确山县| 苍梧县| 罗定市| 云梦县| 隆昌县| 雅江县| 长泰县| 娄烦县| 资阳市| 内乡县| 定襄县| 镇赉县| 淳化县| 湘潭县| 杭锦旗| 安宁市| 滦平县| 利辛县| 金寨县| 远安县| 汤阴县|