作者:小木

首先说下问题现象:内网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的配置应该合理配上,否则出现问题无错误日志,无报错,定位相当困难。


链接:https://my.oschina.net/xiaomu0082/blog/2990388

扩展阅读


  1. 2019,年度数据库舍 PostgreSQL 其谁?

  2. 2019,国产数据库元年开启新纪元

  3. 2019,遇见最好的 MySQL 数据库

  4. 史上最全:PostgreSQL DBA常用SQL查询语句

  5. 2019年12月数据库流行度排行:前三甲高位收官 数据库重获增长趋势

  6. 万字详解Oracle架构、原理、进程,学会世间再无复杂架构

  7. 1000行MySQL学习笔记,收藏版!

  8. 史上最全PostgreSQL体系结构

数据和云

ID:OraNews

如有收获,请划至底部,点击“在看”,谢谢!

资源下载

关注公众号:数据和云(OraNews)回复关键字获取

help,30万+下载的完整菜单栏

2019DTCC,数据库大会PPT

2018DTCC , 数据库大会PPT

2018DTC,2018 DTC 大会 PPT

ENMOBK,《Oracle性能优化与诊断案例》

DBALIFE,“DBA 的一天”海报

DBA04,DBA 手记4 电子书

122ARCH,Oracle 12.2体系结构图

2018OOW,Oracle OpenWorld 资料

产品推荐

云和恩墨BethuneX  企业版,集监控、巡检、安全于一身,你的专属数据库实时监控和智能巡检平台,漂亮的不像实力派,你值得拥有!

云和恩墨zData一体机现已发布超融合版本和精简版,支持各种简化场景部署,零数据丢失备份一体机ZDBM也已发布,欢迎关注。

云和恩墨大讲堂 | 一个分享交流的地方

长按,识别二维码,加入万人交流社群

请备注:云和恩墨大讲堂

  点个“在看” 
 你的喜欢会被看到????

记一次因Redis使用不当导致应用卡死过程相关推荐

  1. 不是keys,记一次因 redis 使用不当导致应用卡死 的过程

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 来源:腾讯技术工程 首先说下问题现象:内网sandbox环境API持续1周 ...

  2. 记一次因 Redis 使用不当导致应用卡死 bug 的排查及解决!

    点击上方"朱小厮的博客",选择"设为星标"后台回复"书",获取后台回复"k8s",可领取k8s资料 首先说下问题现象:内 ...

  3. java redis使用卡死_记一次找因 redis 使用不当导致应用卡死 bug 的过程

    原标题:记一次找因 redis 使用不当导致应用卡死 bug 的过程 作者:小木 my.oschina.net/xiaomu0082/blog/2990388 首先说下问题现象:内网sandbox环境 ...

  4. 记一次找因 Redis 使用不当导致应用卡死 bug 的过程

    首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理.但是后来问题出现频率越来越频 ...

  5. 记一次找因Redis使用不当导致应用卡死bug的过程

    作者:小木 首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理.但是后来问题出现 ...

  6. Redis使用不当导致应用卡死

    作者:小木 来源:http://rrd.me/ezfTj 首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用 ...

  7. 记一次CentOS7因Redis配置不当导致被Root提权沦为矿机修复过程

    未曾想过,那些年影视剧中黑客们的精彩桥段,竟在2020这个充满魔幻的年份,变成了现实. 前几日傍晚突然收到了来自阿里云安全中心的提醒,服务器疑似受到攻击了.想不到我那用作学习的机器,有朝一日竟然沦为矿 ...

  8. 记一次自定义 Redis 分布式锁导致的故障

    点击上方关注 "终端研发部" 设为"星标",和你一起掌握更多数据库知识 背景 企微报警群里连续发出生产环境报错警告,报错核心信息如下: redis setNX ...

  9. 记一次自定义Redis分布式锁导致的生产事件时间

    背景 企微报警群里连续发出生产环境报错警告,报错核心信息如下: redis setNX error java.lang.NumberFormatException: For input string: ...

最新文章

  1. React State和生命周期 3
  2. 这个宝藏工具,给你一种黑客般的感觉
  3. vector int string 化
  4. jpa连接mysql报错_Spring boot jpa mysql 连接数据库SSL错误
  5. 【bzoj2693】jzptab 莫比乌斯反演+线性筛
  6. Neither spark.yarn.jars nor spark.yarn.archive is set
  7. linux错误日志重定向,Linux shell 重定向标准错误输出
  8. Python操作Memcache
  9. C# 读取Execl和Access数据库
  10. Struts2 中的值栈的理解
  11. mybatis实现模糊查询时%%的处理
  12. 计算机枚举法教案,简单枚举算法教案.ppt
  13. 基于android的个人收支财务管理,基于Android的个人财务管理系统的设计与实现.doc...
  14. 微信加人的108种方法
  15. 博饼程序-Java实验
  16. 使用Python第三方库requests和bs4 爬取必应首页的图片,并存储到系统对应位置
  17. scrapy--dytt(电影天堂)
  18. 支配树 / Dominator_Tree(待补)
  19. 二进制文件文本文件和二进制数据
  20. 中英介绍寒食节、清明节的来历及习俗

热门文章

  1. raspberry 防火墙_用于数据科学,Linux防火墙,Raspberry Pi NAS,openmediavault,Syncthing,微服务等的Python库
  2. 让电脑说话代码_让您的代码为您说话
  3. CSS 字体调整 font-size-adjust属性
  4. es6 prototype 属性和__proto__属性
  5. 休息是为了更好的出发
  6. 深度学习笔记(26) 卷积神经网络
  7. ac ap原理、_AP面板是什么?家庭AC+AP的组网方式,真的适合所有人吗?
  8. linux mkswap -p参数,Linux基础命令---mkswap
  9. java发送http跨域_跨域发送HTTP请求详解
  10. Intent.ACTION_PICK