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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2025/3/20 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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。

"pool-13-thread-6" prio=10 tid=0x00007f754800e800 nid=0x71b5 waiting on condition [0x00007f758f0ee000]

java.lang.Thread.State: WAITING (parking)

at sun.misc.Unsafe.park(Native Method)

- parking to wait for <0x0000000779b75f40> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)

at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)

at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:583)

at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442)

at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)

at redis.clients.util.Pool.getResource(Pool.java:49)

at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)

at org.reborndb.reborn.RoundRobinJedisPool.getResource(RoundRobinJedisPool.java:300)

at com.le.smartconnect.adapter.spring.RebornConnectionFactory.getConnection(RebornConnectionFactory.java:43)

at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)

at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)

at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)

at xxx.run(xxx.java:80)

at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)

at java.util.concurrent.FutureTask.run(FutureTask.java:262)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)

at java.lang.Thread.run(Thread.java:745)

Locked ownable synchronizers:

- <0x000000074f529b08> (a java.util.concurrent.ThreadPoolExecutor$Worker)

也就是說,redis連接獲取不到,線程一直在等待可用的redis連接。大概率是應用中有功能模塊獲取到連接,并沒有釋放。找到一個功能使用了scan,具體如下:

public void releaseCallbackMessage() throws Exception {

Cursor> cursor = RedisCacheUtils.scan(key)

if (cursor == null) {

logger.info("通過scan(H key, ScanOptions options)方法獲取匹配鍵值對記錄為空");

return;

}

while (cursor.hasNext()) {

// 遍歷緩存

Map.Entry entry = cursor.next();

String key = String.valueOf(entry.getKey());

}

}

}

查看scan源碼,發現其使用過程中,并未主動釋放connection,而get/set操作均會主動釋放connection

public Cursor> scan(K key, ScanOptions options) {

byte[] rawKey = rawKey(key);

return template.executeWithStickyConnection(

(RedisCallback>>) connection -> new ConvertingCursor<>(connection.hScan(rawKey, options),

new Converter, Entry>() {

@Override

public Entry convert(final Entry source) {

return new Entry() {

@Override

public HK getKey() {

return deserializeHashKey(source.getKey());

}

@Override

public HV getValue() {

return deserializeHashValue(source.getValue());

}

@Override

public HV setValue(HV value) {

throw new UnsupportedOperationException("Values cannot be set when scanning through entries.");

}

};

}

}));

}

get操作源碼finally中有releaseConnection操作。

@Nullable

public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) {

Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");

Assert.notNull(action, "Callback object must not be null");

RedisConnectionFactory factory = getRequiredConnectionFactory();

RedisConnection conn = null;

try {

if (enableTransactionSupport) {

// only bind resources in case of potential transaction synchronization

conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);

} else {

conn = RedisConnectionUtils.getConnection(factory);

}

boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

RedisConnection connToUse = preProcessConnection(conn, existingConnection);

boolean pipelineStatus = connToUse.isPipelined();

if (pipeline && !pipelineStatus) {

connToUse.openPipeline();

}

RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));

T result = action.doInRedis(connToExpose);

// close pipeline

if (pipeline && !pipelineStatus) {

connToUse.closePipeline();

}

// TODO: any other connection processing?

return postProcessResult(result, connToUse, existingConnection);

} finally {

RedisConnectionUtils.releaseConnection(conn, factory);

}

}

3、解決方式

scan操作后,主動關閉游標,使用try(resource) catch(exception)方式編碼。

1、redis scan操作記住需要主動關閉cursor,即cursor.close;

2、加強規范編碼;

try (Cursor> cursor = RedisCacheUtils.scan(key)) {

if (cursor == null) {

logger.info("通過scan(H key, ScanOptions options)方法獲取匹配鍵值對記錄為空");

return;

}

while (cursor.hasNext()) {

// 遍歷緩存

Map.Entry entry = cursor.next();

String key = String.valueOf(entry.getKey());

}

} catch (Exception ex) {

logger.info(ex.toString());

}

關于 try-with-resources用法需要提一點的就是,resources對象必須是實現了 java.lang.AutoCloseable接口,才會自動關閉對象。

補充知識:redis連接未釋放,導致redis連接池滿,從而應用服務不可用的問題定位和解決

版本提交測試驗收后,跑了幾天,今天測試突然跑來說平臺不可用。

1. 我先是試圖登錄平臺,發現首頁可以進入,但是登錄不成功。很顯然是后臺的問題。

2. 再看MQ中,發現消息堆積在隊列中,未被消費掉,同時一點一點變化,說明很有可能是哪里有內存或連接的泄露或未釋放。

3. 接著登錄阿里云賬號,查看redis監控,發現連接數已經達到9000多。

4. 查看日志發現大量的redis連接拒絕錯誤

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

at redis.clients.util.Pool.getResource(Pool.java:42)

at redis.clients.jedis.JedisPool.getResource(JedisPool.java:84)

at com.***(**.java:58)

at com.***(**.java:86)

at com.***(**.java:27)

at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)

at org.apache.log4j.Category.callAppenders(Category.java:206)

at org.apache.log4j.Category.forcedLog(Category.java:391)

at org.apache.log4j.Category.log(Category.java:856)

at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:571)

at com.***(**.java:61)

at com.***(**.java:86)

at com.***(**.java:27)

at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)

at org.apache.log4j.Category.callAppenders(Category.java:206)

at org.apache.log4j.Category.forcedLog(Category.java:391)

at org.apache.log4j.Category.log(Category.java:856)

at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:571)

at com.***(**.java:61)

at com.***(**.java:86)

at com.***(**.java:27)

at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

5. 當然后臺的tomcat中也報了其他錯誤,比如:

Exception in thread "Thread-18" java.lang.StackOverflowError

at java.util.Hashtable.get(Hashtable.java:367)

at java.util.Properties.getProperty(Properties.java:969)

at java.lang.System.getProperty(System.java:720)

at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:86)

at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:52)

at java.security.AccessController.doPrivileged(Native Method)

at java.io.PrintWriter.(PrintWriter.java:116)

at java.io.PrintWriter.(PrintWriter.java:100)

at org.apache.log4j.DefaultThrowableRenderer.render(DefaultThrowableRenderer.java:58)

at org.apache.log4j.spi.ThrowableInformation.getThrowableStrRep(ThrowableInformation.java:87)

at com.aliyun.openservices.log.log4j.LoghubAppender.append(LoghubAppender.java:116)

at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)

at org.apache.log4j.Category.callAppenders(Category.java:206)

at org.apache.log4j.Category.forcedLog(Category.java:391)

at org.apache.log4j.Category.log(Category.java:856)

at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:571)

at com.***(**.java:61)

at com.***(**.java:86)

at com.***(**.java:27)

at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)

at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)

at org.apache.log4j.Category.callAppenders(Category.java:206)

at org.apache.log4j.Category.forcedLog(Category.java:391)

at org.apache.log4j.Category.log(Category.java:856)

6. 但是,還是繼續查看日志,發現一直有個 [TaskId]PUPHVUNVJSSMKOTQPKHRSPOMUKKOKLPG [Message]null [Result]0 debug日志,因為只有一直不停的發,且連接不關閉才可能出現這么多連接的情況。一邊對應代碼,發現是頁面上調用后臺代碼,發送給設備長連接的代碼。

try{

for(...)

{

jedis = RedisManagerPool.getJedis();

if(!"NULL".equals(value)) {

break;

}

RedisUtils.return(jedis);

}

} catch() {

logger.error();

RedisUtils.returnBroken(jedis);

}

return value;

且try中沒有finally塊,很顯然如果條件滿足的話就直接break,并return value。但是RedisUtils.return(jedis)這條語句就未執行。然后進一步懷疑頁面是否是定時去獲取,通過F12,發現每10s鐘請求一次,頁面上要獲取設備的上下行速率。所以會累積這么多的請求。最后,修改也比較簡單,添加finally塊,保證RedisUtils.return(jedis)必定會執行。

8. 接下來在另一個開發環境繼續復現,我們將redisManagerPool中設置maxTotal=300,maxIdle=30。而原先是3000、300,這樣有利于快速復現。

果然,一上午時間就達到了300的限制。出現了一樣的問題。

9. 綜上,問題定位清楚,且修復該問題。

a) 對于oss redis之類的第三方網絡連接,必須要有finally塊執行。否則后續很容易由于不規范的編碼,出現這種連接未正常釋放的問題。

b) 定位問題,還是需要有日志。如果單從代碼去查,方向會比較多且很容易浪費時間。

c) 修改池大小,縮短復現時間,快速定位修改。

以上這篇redis scan命令導致redis連接耗盡,線程上鎖的解決就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

總結

以上是生活随笔為你收集整理的redis 命令 释放连接_redis scan命令导致redis连接耗尽,线程上锁的解决的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。