简介
写这篇文章的初衷源于前几天公司的Redis连接池溢出事件,后来是借助SpringBean的生命周期特征解决了连接池溢出问题。基础稍微好点的人应该知道,spring管理的bean,可以自定义初始化方法init,和bean销毁方法destroy,但是以前只是知道有这些特性,工作中从来没有用过,因此比较陌生。
事件还原
连续两天,公司redis连接池溢出,好多项目各种报错,群上开始讨论redis的问题,有的说扩大连接池连接数量,有的说定时重启redis,在我看来这些都解决不了根本问题,然后我说了下自己的看法,结果公司大佬让我去研究问题出在哪里,原以为问题很严重,结果打开代码一看,原来是工具类的一个低级错误。
Redis原有的工具类如下
Redis配置文件的一部分
![](https://i-blog.csdnimg.cn/blog_migrate/2b2a95cb780bbf9cca36c706f31c5682.png)
分析
很明显,RedisClusterDao工具类并没有加入到spring容器中做统一管理(在配置文件中也没有将Redis工具类加入到Spring容器),其他类在使用的时候都是以new的方式创建新的对象,因此工具类中定义的init方法并不会在new的时候执行,destroy方法也不会在工具类实例销毁时执行,也就是说shardedJedisPool连接池并没有得到释放。
再看配置文件,MaxTotal的值为-1,一般情况下,-1表示不限制,放在这里应该就是表示不限制redis连接的数量,具体含义我并没有细查,也没查到。
同时,公司的这台Redis服务是开发人员本地和测试环境服务器共用的,也就是说,开发人员启动本地服务,然后服务中创建了多个redis连接,接着再把这个服务关掉,此时这几个连接并没有释放,注意配置文件中的timeout是6000秒,即100分钟,假设这个程序员在100分钟之内重启了10次本地服务,一次启动建立10个连接,那么就有100个连接不被释放,注意这100个连接并不是到了100分钟立即全部释放,他是有时间差,假设公司有100个开发人员,那么会产生10000个连接,由于配置文件中并没有限制连接数,极端情况下可能更多,所以连接池溢出是迟早的事。
我的解决方案
A) .控制redis连接池最大连接数量。
B) .将redis工具类加入到Spring容器做统一管理。
C) .为了确保destroy方法执行,可通过Spring事件+监听器再次释放redis连接池。
可行性分析
A方案被大佬质疑,说是担心控制连接数后,应用的连接数会不够
B方案也被大佬否定,因为说之前就是这样做的,说是出什么问题,所以才用现在的方式。我猜想是因为服务运行过程中因为内存问题,工具类的Bean被回收(Spring中即使是Single的单例Bean也会在内存极度匮乏情况下被回收),因此报错。
C方案,由于公司架构使用的不是标准的Spring体系(SpringIOC,SpringAOP+CXFService),因此除过Refresh事件,其他事件并不会产生,比如这里需要用到的stop事件,因此这个方案也是不可行。
最终解决思路
后来,基于原有工具类的灵感,我借助Spring的Bean生命周期特性,添加destroy方法,并且用注解@PreDestroy标识。但是原有的工具类在很多地方有有用到,而且我不知道B方案中大佬所说的问题是什么问题,因此原有的工具类我没有做任何修改。
虽然原有工具类不能动,但是可以注意到,原有工具类中的shardedJedisPool连接池变量是static类型,因此,我重新定义了一个类,如下
将新定义的类加入到Spring容器当中,这样在容器关闭的时候会调用destroy方法,redis连接池也就得到了释放,但是需要注意,为了方式B方案中我的猜想发生(Bean被回收),我在当前类实现了接口ApplicationLister,这是Spring一个非常中要的接口,而且跟事件相关,因此我猜想实现这个接口的Bean全程不会被回收,也就不会发生B方案中猜想的事情发生。
也可以用另外一种方式防止Bean被回收的事件发生,那就是将当前新定义的Bean实例加入到ServletContext当中,这样当前Bean实例就会全程被引用,所以不会被回收,这样做相对比较可靠。