记一次 Redis 连接池泄漏问题排查
这一天风和日丽,我很荣幸的参加进入组织的活动,这个组织依然是一群闷骚的少年,热火朝天的甩着膀子,写着神圣的 Java 代码,偌大的办公室,只能听见噼里啪啦的敲击键盘声!
好骚气的组织!!!
------------------------------------------------------------------------------------------------------------------------
进入组织后,给我随手就撩过来一个 git 地址,习惯性的通过 git clone <git-url> ,将代码 dang 下来!
我的 IDEA 已经饥渴难耐,说时迟那时快。鼠标飞快的飞到桌面的 , 以盲狙的速度进行了双击!经过 10s 的长达等待。主界面终于展示在我的眼前。仿佛看到了我梦中的女神——怦然心动,无法形容我的处境。
作为一个不会技术的二溜子,岂能就此作罢,扫视一眼后发现,原来是仰慕已久的 Web 项目基佬(这么说主要是因为培训机构 3 个月能生产一批,而且都是各行各业的男性同胞),瞬间心情大落!吾等倒要看看你是什么妖孽,没有妖孽也要给你制造一批出来。
拉出我的汤姆猫( tomcat ), 将它迅速加载进来。紧接着一个飘逸的 “Shift + F10” (IDEA 的 快捷键)闪过。
一个 http://localhost:8080 的界面自动打开在我的眼前。
哎呀,好帅气的界面……
输入测试账号、测试密码、登录验证码……一波骚操作之后,功能都可以正常使用!
待我休息三秒后,挠了挠头,对 组织成员 A 说:嗨,帅哥,发 50 个请求过来玩玩!
哈……果然,出现了骚气的问题,页面请求处于 pending 状态,过 10s 报 timeout , 后台日志报错:
org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisException: Could not get a resource from the poolat org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204)at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:348)at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:129)at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92)at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79)at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:194)at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169)at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:91)at org.springframework.data.redis.core.DefaultValueOperations.increment(DefaultValueOperations.java:63)
注:说明一下,我们得 redis 环境配置主要为:
<property name="maxIdle" value="50"/>
<property name="minIdle" value="20"/>
<property name="usePool" value="true" />
----------------------------------------------------------------------------------------------------------------------------------
看到如此之问题,岂不是很蛋疼,立即跳起来,左手叉腰,右手指向天花板。大喊一声:拿我的 5 米长刀来。
我跟着异常中提示的异常堆栈信息,我打开代码,并定位到异常的行数位置,查看代码。大多都是通过 redisTemplate 来与 Redis 交互。redis 的连接池是通过 common-pools 来管理的,redisTemplate 之前我在其他项目也使用过,不应该会出现泄漏的问题。
怀着激动不安的心情,我进去到如下代码中进行了代码跟踪:
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
这里我以 valueOperations.set()进行了代码跟踪, set 方法的实现如下:
public void set(K key, V value) {final byte[] rawValue = rawValue(value);execute(new ValueDeserializingRedisCallback(key) {protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {connection.set(rawKey, rawValue);return null;}}, true);}
追踪代码,set 方法调用内部,我很能确定的是,调用后,链接进行了关闭操作(代码如下)!其实,严格来说,使用连接池,通过 borrowObject()方法获取的,最终当然是通过 returnObject()! 读者有兴趣可以直接了解 : common-pools2.jar 源代码了解。
/*** Executes the given action object within a connection that can be exposed or not. Additionally, the connection can* be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios).* * @param <T> return type* @param action callback object to execute* @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code* @param pipeline whether to pipeline or not the connection for the execution* @return object returned by the action*/public <T> T execute(RedisCallback<T> 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 = getConnectionFactory();RedisConnection conn = null;try {if (enableTransactionSupport) {// only bind resources in case of potential transaction synchronizationconn = 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 pipelineif (pipeline && !pipelineStatus) {connToUse.closePipeline();}// TODO: any other connection processing?return postProcessResult(result, connToUse, existingConnection);} finally {RedisConnectionUtils.releaseConnection(conn, factory);}}
看了看我的 5 米长刀,再看看这个异常,虎躯一震:难不成真要让我的大刀上场?!
干!!!
天下大事,要干成功,一般需要三步:
1. 千军易找,一将难求!(打开终端,找到程序的 pid )
2. 招兵买马、屯田生产(获取应用堆栈信息,命令为: jmap -dump,format=b,file=/home/hadoop/my-dump.hprof <pid> )
3. 拿下城池,弑帝称王!(使用 Mat 分析定位、解决问题)
注: MAT 全称为:Eclipse Memory Analyzer, 下载地址为:http://www.eclipse.org/mat/ , 读者下载完后,可以考虑修改一下 MemoryAnalyzer.ini 文件中 -Xmx 的大小( 如果你的 hprof 很大的话,会造成 OOM,导致无法继续分析 )
--------------------------------------------------------------------------------------------------------------------------
我的文件打开后,如下图,占用内存并不大。我们主要是分析链接泄漏。既然是泄漏,肯定就存在有内存不能释放,并且可 DUMP 。
点击内存占用最高的饼图位置,会出现如下图示的菜单可选择。选择 "show objects by class " -> “ by incoming references ” !
随后会打开 "class references" 窗口, 果断的在 ClassName 顶部的搜索框中输入 “com” ( 我们只关注我们关注的内容),由于是连接池泄漏,所以其他的内容我们可以不用理会了哈,直接看 org.apache.commons.pool2.impl.GenericObjectPool 即可。
在该类上面右击,选择 “Java Basics” ——> "Open In Dominator Tree" ,打开 !
一步步的展开 org.apache.commons.pool2.impl.GenericObjectPool 我们可以看到如下图。哈,,,大大的 hscan !
到这一步,我们已经很清晰了。 在 IDEA 中搜索 hscan 相关的代码。果然,找到了一些使用 scan 命令的地方,再细细端详才发现, 罪魁祸首为:
Cursor<Map.Entry<String, Bean>> cursor = hashOperations.scan(scanOption);
cursor 使用完毕后,没有看到调用 .close() 的地方。
果断加上,再测一把!!!顺利通过。
结论:
在平时的代码中,一定要记得在使用链接、游标、流等位置,记得关闭!否则会造成不可预料的问题。
问题顺利解决,收起我的 5 米大刀!
端起我的碧螺春,轻抿一口!
窗外不知何时漂起了小雨!
转载于:https://my.oschina.net/Rayn/blog/2032408
记一次 Redis 连接池泄漏问题排查相关推荐
- python redis连接池获取后关闭_python通过连接池连接redis,操作redis队列
在每次使用redis都进行连接的话会拉低redis的效率,都知道redis是基于内存的数据库,效率贼高,所以每次进行连接比真正使用消耗的资源和时间还多.所以为了节省资源,减少多次连接损耗,连接池的作用 ...
- Java的Redis连接池代码性能不错
其实这个是引用自网友http://blog.csdn.net/tuposky/article/details/45340183,有2个版本,差别就是ReentrantLock和synchronized ...
- redis连接池操作
/** * @类描述 redis 工具 * @功能名 POJO * @author zxf * @date 2014年11月25日 */ public final class RedisUtil { ...
- java操作redis redis连接池
redis作为缓存型数据库,越来越受到大家的欢迎,这里简单介绍一下java如何操作redis. 1.java连接redis java通过需要jedis的jar包获取Jedis连接. jedis-2.8 ...
- redis专题:redis键值设计、性能优化以及redis连接池配置
文章目录 1.redis键值设计 ①:key设计规范 ②:value设计规范 2. 命令使用优化 3. redis连接池配置参数设计 4. redis连接池预热 5. redis的key过期删除策略 ...
- Java Redis 连接池 Jedis 工具类,java基础面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- Java的Redis连接池代码
2019独角兽企业重金招聘Python工程师标准>>> 其实这个是引用自网友http://blog.csdn.net/tuposky/article/details/45340183 ...
- php redis 集群 长连接池,php如何实现redis连接池
项目使用的是php,生产环境使用的是redis集群,连接的地址是配置的域名,每次创建连接必须要经过一次域名解析,频繁的创建链接效率低下且经常出现超时的情况,有没有在生产环境实现redis链接池的,分享 ...
- SpringBoot 配置 Redis 连接池
前言 SpringBoot2.0默认采用 Lettuce 客户端来连接 Redis 服务 默认是不使用连接池的,只有配置 redis.lettuce.pool下的属性的时候才可以使用到redis连接池 ...
最新文章
- Java线程:线程的调度-合并
- 如何获取 Process.Start 打开进程的输出结果?
- [Err] 1231 - Variable 'sql_mode' can't be set to the value of 'NULL
- scrapy自定义Request的缓存策略(减少内存占用)
- Keras-Sequential模型(1)
- vc mysql query_我要使用mysql_query()这个函数,在VC中需要做什么工作?
- 同义句转换在线翻译器的软件
- 大学统计学基础知识笔记
- axios 最详细封装
- 浅谈计算机网络安全问题和对策
- 计算机本地连接无internet访问权限,ipv4连接无internet访问权限怎么解决
- iphone/ipad保存图片问题
- Unity制作UI翻页动画
- linux正在等待声音系统响应,linux声音系统较好的解决方案alsa+esd
- usb无线网卡和U盘同时使用
- 机械键盘各种轴的特点
- Linux 文件与目录的管理
- RAAVPPSPSLSRHSSPHQSEDEEE
- java入门考点_java入门基础知识点总结
- python习题练习
热门文章
- input file图片上传(使用OSS Javscrtipt 上传到服务器)以及图片裁剪(cropper.js)
- sed在shell脚本中引用变量
- php短信炸弹,php发送短信炸弹
- 辉煌一时的金立如今却沦为山寨机?只因做错了这一点
- LaTeX环境安装及入门之入门使用
- Acwing---1231.航班时间
- 访问者模式-好人打贱人
- eclipse配置opencv和javacv环境
- ERROR:Session/line number was not unique in database. History logging moved to new session.
- 单目标应用:基于北方苍鹰优化算法NGO的概率神经网络PNN数据分类(提供MATLAB代码)