记一次找因Redis使用不当导致应用卡死bug的过程
作者:小木
首先说下问题现象:内网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
另:想了解更多 Redis 数据库的知识与用法,欢迎关注墨天轮“Redis 专栏”(地址:https://www.modb.pro/db,点击左下角的“阅读原文”或者扫描下方二维码可直达),此外,墨天轮开放了很多数据库专栏,如 GaussDB、PolarDB、OceanBase、TDSQL、GoldenDB 等众多数据库专栏,欢迎关注学习!
扩展阅读
2019,年度数据库舍 PostgreSQL 其谁?
2019,国产数据库元年开启新纪元
2019,遇见最好的 MySQL 数据库
史上最全:PostgreSQL DBA常用SQL查询语句
2019年12月数据库流行度排行:前三甲高位收官 数据库重获增长趋势
万字详解Oracle架构、原理、进程,学会世间再无复杂架构
1000行MySQL学习笔记,收藏版!
史上最全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使用不当导致应用卡死bug的过程相关推荐
- java redis使用卡死_记一次找因 redis 使用不当导致应用卡死 bug 的过程
原标题:记一次找因 redis 使用不当导致应用卡死 bug 的过程 作者:小木 my.oschina.net/xiaomu0082/blog/2990388 首先说下问题现象:内网sandbox环境 ...
- 记一次找因 Redis 使用不当导致应用卡死 bug 的过程
首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理.但是后来问题出现频率越来越频 ...
- 记一次因 Redis 使用不当导致应用卡死 bug 的排查及解决!
点击上方"朱小厮的博客",选择"设为星标"后台回复"书",获取后台回复"k8s",可领取k8s资料 首先说下问题现象:内 ...
- 不是keys,记一次因 redis 使用不当导致应用卡死 的过程
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 来源:腾讯技术工程 首先说下问题现象:内网sandbox环境API持续1周 ...
- 记一次因Redis使用不当导致应用卡死过程
作者:小木 首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理.但是后来问题出现 ...
- 记一次CentOS7因Redis配置不当导致被Root提权沦为矿机修复过程
未曾想过,那些年影视剧中黑客们的精彩桥段,竟在2020这个充满魔幻的年份,变成了现实. 前几日傍晚突然收到了来自阿里云安全中心的提醒,服务器疑似受到攻击了.想不到我那用作学习的机器,有朝一日竟然沦为矿 ...
- Redis使用不当导致应用卡死
作者:小木 来源:http://rrd.me/ezfTj 首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用 ...
- MemoryCache 使用不当导致的一个 BUG
MemoryCache 使用不当导致的一个 BUG Intro 前几天发现代码里的一个 BUG,原因是 MemoryCache 使用不当,可以对于很多人来说可能都知道,但还是想分享记录一下,避免以后写 ...
- Redis配置不当致使root被提权漏洞
Redis配置不当致使root被提权漏洞 Dear all~ 最近Redis服务被曝出因配置不当,可能造成数据库被恶意清空,或被黑客利用写入后门文件造成进一步入侵,请关注! 一.漏洞发布日期 2015 ...
最新文章
- 22. datenum Matlab中的日期型数据处理函数
- 【每日一算法】罗马数字转整数
- win7下搭建手动转码服务器的安装配置tomcat、java、ffmpeg、hy_changsha、FTP
- ws.v6p.coindex.php,php_168_v6目录
- Docker的特性解析
- ul li前面的点怎么变大_硅片尺寸变大,生产成本降低,光伏电池生产商转向更大尺寸硅片...
- 信息学奥赛一本通(1218:取石子游戏)
- STM32使用485接口通讯
- Flink窗口-时间窗口
- disk dynamic invalid 解决办法 动态磁盘转换器
- 基于扩张卷积神经网络的图像超分辨率
- java 生成纯色图片_浅谈Java设置PPT幻灯片背景——纯色、渐变、图片背景
- premiere(Pr)为视频配字幕 开放式字幕使用详解
- 江西省中小学生学籍管理-登录(1)
- 计算机如何增加c盘容量,怎么给c盘增加空间 c盘增加空间步骤【图文】
- dedecms织梦后台账号或密码忘记了怎么办?
- 手动挡你会开吗 八招教你开好手动挡车型
- selenium之find_element_by_xpath定位元素
- 【正点原子FPGA连载】 第一章 MPSoC简介 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
- python ocr识别身份证_不告诉你我用了它配合Python简简单单开发OCR识别,带你识别手写体、印刷体、身份证等N种,附代码!...
热门文章
- 安全和隐私:您知道系统中潜藏着什么吗?
- groovy语言 累加_使用Groovy管理数据:查找和累加器
- CSS 浮动 float属性
- CSS 框的生成:块级元素和块框
- es6 async函数的异步迭代器
- mysql rls_Python插入到mysql表不起作用
- vrep中的a dubins state space
- sperling指标 matlab,sperling指标计算实验报告
- java学习是什么_学习JAVA有什么作用?
- 2019长江课堂作业答案_“绝户网”捕捞长江鳗鱼苗 检察机关:“全链条”担责...