Redis使用不当导致应用卡死


作者:小木

来源:http://rrd.me/ezfTj

首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象

刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理。但是后来问题出现频率越来越频繁,越来越多的同事开始抱怨,于是感觉代码可能有问题,开始排查。

首先发现开发的本地ide没有发现问题,应用卡死时候数据库,redis都正常,并且无特殊错误日志。开始怀疑是sandbox环境机器问题(测试环境本身就很脆!_!)

于是ssh上了服务器 执行以下命令

top

这时发现 机器还算正常,但是内心还是????,于是打算看下jvm堆栈信息

先看下问题应用比较耗资源的线程

执行 top -H -p 12798

找到前3个相对比较耗资源的线程

jstack 查看堆内存

jstack 12798 |grep 12799的16进制 31ff

没看出什么问题,上下10行也看看 于是执行

看到一些线程都是处于lock状态。但没有出现业务相关的代码,忽略了。这时候没有什么头绪。思考一番。决定放弃这次卡死状态的机器

为了保护事故现场 先 dump了问题进程所有堆内存,然后debug模式重启测试环境应用,打算问题再显时直接远程debug问题机器

第二天问题再现,于是通知运维nginx转发拿掉这台问题应用,自己远程debug tomcat。

自己随意找了一个接口,断点在接口入口地方,悲剧开始,什么也没有发生!API等待服务响应,没进断点。这时候有点懵逼,冷静了一会,在入口之前的aop地方下了个断点,再debug一次,这次进了断点,f8 N次后发现在执行redis命令的时候卡主了。继续跟,最后在到jedis的一个地方发现问题:

 

/*** Returns a Jedis instance to be used as a Redis connection. The instance can be newly created or retrieved from a* pool.* * @return Jedis instance ready for wrapping into a {@link RedisConnection}.*/
protected Jedis fetchJedisConnector() {try {if (usePool && pool != null) {return pool.getResource();}Jedis jedis = new Jedis(getShardInfo());// force initialization (see Jedis issue #82)jedis.connect();return jedis;} catch (Exception ex) {throw new RedisConnectionFailureException("Cannot get Jedis connection", ex);}
}

上面pool.getResource()后线程开始wait

public T getResource() {try {return internalPool.borrowObject();} catch (Exception e) {throw new JedisConnectionException("Could not get a resource from the pool", e);}
}

return internalPool.borrowObject();这个代码应该是一个租赁的代码接着跟

public T borrowObject(long borrowMaxWaitMillis) throws Exception {this.assertOpen();AbandonedConfig ac = this.abandonedConfig;if (ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) {this.removeAbandoned(ac);}PooledObject<T> p = null;boolean blockWhenExhausted = this.getBlockWhenExhausted();long waitTime = 0L;while(p == null) {boolean create = false;if (blockWhenExhausted) {p = (PooledObject)this.idleObjects.pollFirst();if (p == null) {create = true;p = this.create();}if (p == null) {if (borrowMaxWaitMillis < 0L) {p = (PooledObject)this.idleObjects.takeFirst();} else {waitTime = System.currentTimeMillis();p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);waitTime = System.currentTimeMillis() - waitTime;}}if (p == null) {throw new NoSuchElementException("Timeout waiting for idle object");}

其中有段代码

if (p == null) {if (borrowMaxWaitMillis < 0L) {p = (PooledObject)this.idleObjects.takeFirst();} else {waitTime = System.currentTimeMillis();p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);waitTime = System.currentTimeMillis() - waitTime;}
}

borrowMaxWaitMillis<0会一直执行,然后一直循环了 开始怀疑这个值没有配置

找到redis pool配置,发现确实没有配置MaxWaitMillis,配置后else代码也是一个Exception 并不能解决问题

继续F8 

public E takeFirst() throws InterruptedException {this.lock.lock();Object var2;try {Object x;while((x = this.unlinkFirst()) == null) {this.notEmpty.await();}var2 = x;} finally {this.lock.unlock();}return var2;
}

到这边 发现lock字眼,开始怀疑所有请求api都被阻塞了

 

于是再次ssh 服务器 安装 arthas ,(Arthas 是Alibaba开源的Java诊断工具)

执行thread命令 

 

发现大量http-nio的线程waiting状态,http-nio-8083-exec-这个线程其实就是出来http请求的tomcat线程

随意找一个线程查看堆内存

thread -428

这是能确认就是api一直转圈的问题,就是这个redis获取连接的代码导致的,

解读这段内存代码  所有线程都在等 @53e5504e这个对象释放锁。于是jstack 全局搜了一把53e5504e ,没有找到这个对象所在线程。

自此。问题原因能确定是 redis连接获取的问题。但是什么原因造成获取不到连接的还不能确定

再次执行 arthas 的thread -b (thread -b, 找出当前阻塞其他线程的线程)

没有结果。这边和想的不一样,应该是能找到一个阻塞线程的,于是看了下这个命令的文档,发现有下面的一句话

好吧,我们刚好是后者。。。。

再次整理下思路。这次修改redis pool 配置,将获取连接超时时间设置为2s,然后等问题再次复现时观察应用最后正常时干过什么。

添加一下配置

JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
.......
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxWaitMillis(2000);
.......
jedisConnectionFactory.afterPropertiesSet();

重启服务,等待。。。。

又过一天,再次复现。

ssh 服务器,检查tomcat accesslog ,发现大量api 请求出现500。

org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource fr
om the poolat org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140)at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229)at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57)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 org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177)at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152)at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:85)at org.springframework.data.redis.core.DefaultHashOperations.get(DefaultHashOperations.java:48)

找到源头第一次出现500地方,发现以下代码

.......
Cursor c = stringRedisTemplate.getConnectionFactory().getConnection().scan(options);
while (c.hasNext()) {
.....,,}

分析这个代码

stringRedisTemplate.getConnectionFactory().getConnection()获取pool中的redisConnection后,并没有后续操作,也就是说此时redis 连接池中的链接被租赁后并没有释放或者退还到链接池中,虽然业务已处理完毕 

redisConnection 已经空闲,但是pool中的redisConnection的状态还没有回到idle状态

正常应为

自此问题已经找到。

总结:spring stringRedisTemplate 对redis常规操作做了一些封装,但还不支持像 Scan SetNx等命令,这时需要拿到jedis Connection进行一些特殊的Commands

使用 stringRedisTemplate.getConnectionFactory().getConnection() 是不被推荐的

我们可以使用

 

stringRedisTemplate.execute(new RedisCallback<Cursor>() {@Overridepublic Cursor doInRedis(RedisConnection connection) throws DataAccessException {return connection.scan(options);}})


来执行,

或者使用完connection后 ,用

RedisConnectionUtils.releaseConnection(conn, factory);

来释放connection.

同时,redis中也不建议使用keys命令,redis pool的配置应该合理配上,否则出现问题无错误日志,无报错,定位相当困难。

【END】关注下方二维码,订阅更多精彩内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里巴巴Linux开源镜像站 华为Linux开源镜像站 Linux宝塔面板

学习Linux系统和用Linux系统作服务器时&#xff0c;推荐Linux从Linux系统开源镜像站下载&#xff0c;国内源下载速度快&#xff0c;Linux系统可选择方案多&#xff01; 1.阿里巴巴开源镜像站&#xff1a; https://developer.aliyun.com/mirror/ 2.华为开源镜像站&#xff1a; …

阿里Java开发手册——如何优化数据库?

作者&#xff1a;杨冠宝/高海慧来自&#xff1a;码出高效 Java 开发手册数据库作为服务器端最为最为昂贵的资源之一&#xff0c;如果使用不当常常会导致系统卡顿或系统崩溃&#xff0c;那如何来优化数据库呢&#xff1f;下面来看阿里巴巴《Java开发手册》给出的优化方案。1 建立…

虚拟机VMware Workstation安装Linux服务器Debian11系统详细教程

虚拟机VMware Workstation安装Linux服务器Debian11系统详细教程如下&#xff1a; 准备好&#xff1a; 1.安装好虚拟机VMware Workstation 2.从Linux开源镜像下载的debian11镜像系统 从创建新的虚拟机开始 安装完系统重启进入系统&#xff0c;首先要做的是更新为阿里云的…

借力 Docker ,三分钟搞定 MySQL 主从复制!

hello 各位小伙伴大家好&#xff0c;今年 5 月份的时候&#xff0c;松哥和大家聊过如何搭建 MySQL 主从复制&#xff1a;提高性能&#xff0c;MySQL 读写分离环境搭建(一)提高性能&#xff0c;MySQL 读写分离环境搭建(二)不过很多小伙伴反映在 Linux 中安装 MySQL 比较费劲&…

阿里云服务器Debian11系统安装Linux宝塔面板 搭建WordPress个人博客

阿里云服务器可以使用Debian11镜像系统&#xff0c;在安装Linux宝塔面板&#xff0c;配置LNMP(LinuxNginxMySQLPHP&#xff09;后&#xff0c;再搭建WordPress个人博客。 一、安装Linux宝塔面板&#xff1a; https://www.bt.cn/ 复制debian安装linux宝塔面板命令&#xff0c…

面试官:聊一下你对MySQL索引的理解?

作者&#xff1a;浪人来源&#xff1a;http://t.cn/AiKmcEefMySQL索引&#xff1f;这玩意儿还能简单聊&#xff1f;明显是在挖坑&#xff0c;幸好老夫早有准备&#xff0c;切听我一一道来。 一、索引是什么?索引是帮助MySQL高效获取数据的数据结构。二、索引能干什么?索引非常…

Linux新建用户可以在shell中切换到该用户也能登录到图形桌面

解决使用useradd testname&#xff0c;passwd testname命令确实创建了一个用户&#xff0c;可以在shell中切换到该用户。但不能登录到图形桌面的问题&#xff01; 通过下面方法可以在Linux中新建用户&#xff0c;并创建用户主目录&#xff0c;也能登录到图形桌面 1.普通用户先…

这样写Java,同事直呼666

作者&#xff1a;涛姐涛哥来源&#xff1a;cnblogs.com/taojietaoge/p/11575376.html一、MyBatis 不要写 11当遇到多个查询条件&#xff0c;使用where 11 可以很方便的解决我们的问题&#xff0c;但是这样很可能会造成非常大的性能损失&#xff0c;因为添加了 “where 11 ”的过…

Lync Server 2010 安装部署系列三:添加DNS记录

为了让客户端能够自动登录系统&#xff0c;我们需要在DNS服务器上设置创建相关的SRV记录&#xff0c;首先在DNS服务器上创建一条A记录pool.Contoso.com对应IP地址为192.168.1.11&#xff0c;然后创建一条SRV记录对应刚才创建的A记录&#xff0c;服务的内容为_sipinternaltls&am…

Linux系统下MySQL导出数据库和导入数据库的命令

一、MySQL导出数据库用mysqldump命令 注意mysql的安装路径&#xff0c;即此命令的路径&#xff0c;如果你只要查询mysql的运行文件所在地址&#xff0c;直接用下面的命令就可以了&#xff0c;再切换到mysql的运行文件的路径 which mysqlcd /usr/bin/1、导出数据和表结构 命令格…

工作中 99% 会用到的 Git 命令

作者&#xff1a;命中水来源&#xff1a;https://www.cxiansheng.cn/daily/4901.分支操作git branch 创建分支git branch -b 创建并切换到新建的分支上git checkout 切换分支git branch 查看分支列表git branch -v 查看所有分支的最后一次操作git branch -vv 查看当前分支git b…

JSP tomcat 更新不生效

2019独角兽企业重金招聘Python工程师标准>>> 今天遇到一问题&#xff0c;更改好的jsp部署到tomcat后&#xff0c;无论如何页面也不生效&#xff0c;最后才知道是tomcat的work目录没有清的原因&#xff0c;借此机会也特地看了下work目录的作用以及原理&#xff0c;做…

Win10专业版系统Docker安装、配置和使用详细教程

一、win10专业版系统首先需要开启硬件虚拟化及Hyper-V功能&#xff0c;才能进行Docker for Windows软件安装。 如何开启硬件虚拟化&#xff0c;自行百度。可在任务栏中查看虚拟化是否开启。 win10系统&#xff0c;打开控制面板-“应用”-“程序和功能”&#xff0c;开启Hyper…

框架开发之Java注解的妙用

作者&#xff1a;locality来源&#xff1a;https://www.jianshu.com/p/b560b30726d4如果你还不会使用注解&#xff0c;你肯定不好意思对别人说你学过Spring&#xff0c;你学过Mybatis&#xff0c;因为它们用了大量的注解。可见注解在开发领域已经使用的非常广泛了。注解的好处&…

Win10专业版系统PyCharm专业版使用WSL(ubuntu20.04 LTS)配置Docker解释器配置环境详细教程

提前准备好环境: Win10x64专业版21H2; WSL的ubuntu20.04系统; PyCharm2020专业版; Docker for Windows软件稳定版。 一、Win10系统安装WSL(ubuntu20.04 LTS)子系统 1.按照下图,开启“适用于Linux的Windows子系统”和“虚拟机平台”功能,按照提示重启计算机。 2.在Mic…

Mybatis:颠覆你心中对事务的理解

作者&#xff1a;祖大俊来源&#xff1a;my.oschina.net/zudajun/blog/6667641.说到数据库事务&#xff0c;人们脑海里自然不自然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。四大还好说&#xff0c;问题是七大传播特性是哪儿来的&#xff1f;是Spring在当前线程…

Docker镜像和容器常用命令

一、.Docker帮助命令 1.显示docker的版本信息 docker version 2.显示docker的系统信息&#xff0c;包括镜像和容器的数量 docker info3.docker帮助命令 docker 命令 --help二、Docker镜像命令 1.查看所有本地的主机上的镜像 docker images实例测试&#xff1a; 2.搜索镜像…

如何学会阅读源码?

作者 | youzhibing链接 | cnblogs.com/youzhibing/p/9553752.html1.读源码的经历刚参加工作那会&#xff0c;没想过去读源码&#xff0c;更没想过去改框架的源码&#xff1b;总想着别人的框架应该是完美的、万能的&#xff0c;应该不需要改&#xff1b;另外即使我改了源码&…

求模和求余

一直以为求模和求余是一回事&#xff0c;发现这两者是不同的。以下为网上转载的资料&#xff1a; 通常情况下取模运算(mod)和求余(rem)运算被混为一谈&#xff0c;因为在大多数的编程语言里&#xff0c;都用%符号表示取模或者求余运算。在这里要提醒大家要十分注意当前环境下%运…

利用Dockefile将Python的py文件项目代码打包为Docker镜像

1.创建python项目 【备注&#xff1a;一定要将项目python环境依赖存至本项目下&#xff0c;默认依赖本机python环境(会造成依赖包过多)】 2.创建main.py文件&#xff0c;完成程序代码 主要功能就是获取"https://www.hao123.com/"网址页面源代码&#xff0c;并存储…