使用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连接耗尽,线程上锁的解决相关推荐

  1. redis 内存不足 排查_Redis 系统学习之 redis 内存模型

    关注:架构师学习路线,每日更新互联网最新技术文章与你不断前行,实战资料,笔试面试 前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并 ...

  2. 微软服务器连接失败,Win10更新导致TLS连接失败或连接超时 微软已解决

    11月7日消息  微软在win10仪表盘日志中确认最新累积更新可能导致某些不支持扩展主密钥的客户端出现安全连接超时.这个问题实际上是微软修复CVE-2019-1318安全漏洞导致的,攻击者利用这个漏洞 ...

  3. java redis释放连接_redis在应用中使用连接不释放问题解决

    今天测试,发现redis使用的时候,调用的链接一直不释放.后查阅蛮多资料,才发现一个配置导致的.并不是他们说的服务没有启动导致的. 1)配置文件 #redis连接配置================= ...

  4. redis 清空db下_Redis常用命令集,清空redis缓存数据库

    清空数据库: flushdb   // 清除当前数据库的所有keys flushall    // 清除所有数据库的所有keys Redis常用命令集,清空redis缓存数据库 1)连接操作命令qui ...

  5. Redis mysql查询结果_Redis Select 命令

    Redis Select 命令 - 切换到指定的数据库 Redis Select 命令用于切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值. 语法 redis Sel ...

  6. Redis大key问题与scan命令

    简介 前面不是写了一篇点赞功能的一种实现的文章吗 当时也提出了一些问题,今天就来解决其中的部分问题 开始 先讲一讲背景吧,以免没看过之前文章的迷惑 还是以点赞功能为话题,这里主要解决之前存在的大key ...

  7. mysql redis集群 同步_redis集群和redis主从同步的区别

    很多人认为redis集群就是redis主从同步,其实redis集群跟redis主从同步的机制完全不一样. 1.redis集群包含主从同步:假如你配置了6个节点的redis-server做集群,那么使用 ...

  8. Redis Scan 命令

    文章目录 1. Scan 命令的基本用法 1.1 Scan 命令的输入值 1.2 Scan 命令的返回值 2. Scan 命令与 Keys 命令比较 2.1 Keys 命令的缺点 2.2 Scan 命 ...

  9. HBase Scan命令详解

    hbase中scan命令是我们经常使用到的,而filter的作用尤其强大.这里简要的介绍下scan下filter命令的使用. 插入scan命令需要的数据 这里模拟了部分微博评论的数据,然后使用代码插入 ...

最新文章

  1. [bzoj 4869] [六省联考2017] 相逢是问候
  2. java中所有函数都是虚函数_关于Java:虚拟函数与纯虚函数之间的区别是什么?...
  3. Windows安装Redis(转!)
  4. Java 压缩解压字符串(支持中文)
  5. Vmware安装Centos7上网问题的解决
  6. ML、DL、CNN学习记录1
  7. DeFi借贷协议Liquity宣布主网将于4月5日上线
  8. Bootstraphead里的内容
  9. 中医预约管理系统都需要哪些功能?
  10. html微博图片上传,如何使上传新浪微博照片更清晰
  11. 小程序apkg还原_谈一谈还原解包后小程序页面wxss样式的若干方法
  12. 设计模式之----依赖倒置(Dependency inversion principle)的理解
  13. 全屏动态滑稽网站HTML源码
  14. 【Eternallyc】函数PlaySound和sndPlaySound
  15. java 水晶按钮_java渲染水晶按钮
  16. Python实现久坐提醒小助手程序
  17. 用电脑怎么快速抠图?怎么把图片抠成透明背景?
  18. gt 630 linux驱动下载,Ubuntu 13.04 双显卡安装NVIDIA GT 630M驱动
  19. 在ROS平台上标定普通摄像头与Kinect摄像头
  20. Android开发必须会的技能!写给安卓软件工程师的3条建议,Android岗

热门文章

  1. 自动打开WINDWOS远程控制的注册表文件
  2. js 数组/对象/日期的浅克隆
  3. 杰和弯道超车 推企业级NAS存储应用方案
  4. docker 安装redis第三方集群方案 codis
  5. 公司--下载svg图片
  6. SQLite数据库介绍
  7. 解决Windows下Arm下Linux下Qt4程序的中文乱码问题
  8. 什么是你的职涯“本钱”
  9. MYSQL 如果把数据文件保存到其他磁盘里
  10. python openoffice_windows下安装UNO,配置AEROO_REPORT (Openoffice4已经升级为Python2.7.5版)...