日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

a频繁连接不上redis_解决Redis连接无法正常释放的问题

發(fā)布時間:2023/12/8 数据库 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 a频繁连接不上redis_解决Redis连接无法正常释放的问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

錯誤信息:

IllegalStateException: Invalidated object not currently part of this pool

一、問題描述

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

如下:

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

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

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

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

看代碼(RedisJedisPool未優(yōu)化之前):當(dāng)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

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

reds連接池10自然就報錯,說:

IllegalStateException: Invalidated object not currently part of this pool

三、解決辦法

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

ThreadLocal jedisHolder = new ThreadLocal();

public final ShardedJedis getShardedJedis() {

return jedisHolder.get();

}

public final void setShardedJedis(ShardedJedis jedis) {

jedisHolder.set(jedis);

}

/**

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

}

/**

*

* 不同前綴的表中可能有相同的鍵,同一個表中也可能是有重復(fù)的baseKey時,用這個生成redis的key 比如 用戶信息表的

* username字段,不同的用戶信息表允許重復(fù)的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)容了,希望能給大家一個參考,也希望大家多多支持。

總結(jié)

以上是生活随笔為你收集整理的a频繁连接不上redis_解决Redis连接无法正常释放的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。