• 记一次解决postgresql数据库内存泄露的问题


    起因

    pg数据库的连接无法回收,并且某一连接如果查询的次数过度会占用很多的内存,最终导致内存溢出

    解决思路

    利用Druid的过滤器的机制,先找到统计连接的使用次数的参数,设定到一定次数之后手动断开连接.

    开始解决

    选择了statementExecuteQueryAfter()这个钩子函数作为切入点,这个函数是在执行完事务之后调用的,获取到了连接执行connection.close(),查看druid的监控和数据库发现数据连接的pid变更了,证明connection.close()的关闭方法是有效的.但是关闭后会报一个数据库连接已关闭的异常,暂时放到后面解决.

    第二步要找到连接的使用次数,因为并不是每个连接查询完都需要进行关闭,而是查询到一定次数之后,最后在connectionHolder.getUseCount()方法中获取到了useCount.

    第一次测试,发现连接不见了

    单线程的测试通过了,进行并发测试发现.close()的方法关闭后最后池里只会有一个连接不断地销毁创建.所有的线程会争抢这一个连接

    更换钩子函数

    查看代码的过程看到了更符合业务需求的钩子函数dataSource_releaseConnection,从这个方法中去断开连接似乎是个更好的选择

    尝试使用Abandoned解决问题

    鉴于close()方法会抛出异常并且不会创建连接,换了一个思路尝试使用druid自己的机制尝试进行连接的回收.

    进行测试发现该方法并没有像想象中的进行回收

    再次尝试Connection.close()

    abandond()方法解决无果,再次尝试Connection.close(),现在有两个问题亟需解决,一个是连接关闭后需要再生成一个新的连接添加回池里面解决Connection.close()到一定程度后池里只有一个连接的问题,第二个问题就是解决抛异常的问题.

    解决没有连接的问题: 撸源码找到了DruidDataSource的createPhysicalConnection()方法,和protected修饰的put方法,既然是protected,第一反应自定义一个MyDruidDataSource实现put方法

    自定义MyDruidDataSource

      @Override
        public boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
            return super.put(physicalConnectionInfo);
        }
    
    

    进行测试,发现可以正常的put连接,解决了.close()删除连接的问题.

    开始解决第二个抛出异常的问题: 异常是在下面这段代码抛出去的

    super.dataSource_releaseConnection(chain, connection);
    

    追踪源码发现,会检查线程是否会关闭,如果关闭了则抛出连接已经关闭的错误.

    解决方法: 既然已经关闭了连接那就不需要往下执行recycle方法回收连接了,所以当我们关闭连接的时候直接return,不再执行recycle方法

    更换Connetion.close(),Druid有更优雅的关闭连接的方法:druidDataSource.discardConnection(connection.getConnectionHolder()); 该方法可以更新活跃数量

    释放代码:

       public void dataSource_releaseConnection(FilterChain chain, DruidPooledConnection connection) throws SQLException {
            if (connection != null && connection.getConnectionHolder() != null && connection.getConnectionHolder().getUseCount() >= 1) {
                System.out.println("-----------存活超过 maxUseCount 使用次数限制,强制释放连接------------");
                //存活超过 maxUseCount 使用次数限制,强制释放连接
                    DruidAbstractDataSource.PhysicalConnectionInfo physicalConnection = connection.getConnectionHolder().getDataSource().createPhysicalConnection();
                    MyDruidDataSource druidDataSource = (MyDruidDataSource) connection.getConnectionHolder().getDataSource();
                    druidDataSource.put(physicalConnection);
                    druidDataSource.discardConnection(connection.getConnectionHolder());
                return;
            }
    
            super.dataSource_releaseConnection(chain, connection);
        }
    

    注意要把MyDruidDataSource注入到容器.

    springboot测试成功

    进行正常测试通过,进行疲劳测试通过, 连接被关闭,内存稳定没有oom的问题.成功.

    和公司框架进行集成

    和公司框架进行集成,发现公司框架自定义了druid的配置类,如果使用现在的自定义的MyDruidDataSource的方法和公司框架有冲突.需要修改公司框架的源码,并且后期维护是个问题.

    考虑使用反射的方式解决与公司框架集成的问题.

    使用反射,直接反射put方法,不需要使用自定义的MyDruidDataSource实现了.无缝与公司框架集成

     Class<?> directDataSourceClass = druidDataSource.getClass().getSuperclass();
                        Method put = directDataSourceClass.getDeclaredMethod("put", DruidAbstractDataSource.PhysicalConnectionInfo.class);
                        put.setAccessible(true);
                        put.invoke(druidDataSource, physicalConnection);
    

    总结

    目前测试表现稳定,数据库连接按照期望的方式回收了,没有再出现过oom,解决问题的过程中遇到了很多的问题,不断的翻源码找到合适的方法,问题得到了解决.

    喜欢我的博客就请点赞+关注一波

  • 相关阅读:
    07. pt-fifo-split
    05. pt-diskstats
    06. pt-duplicate-key-checker
    坑爹的tp-link管理密码设置
    windows核心编程 第5章job lab示例程序 解决小技巧
    FormatMessage将错误代码转换成对应的字符串
    调试 内存查看StringCchCopy的运行前后
    对硬盘扇区的操作,练手代码
    关不掉的窗口
    读取unicode日志文件并清除记录的垃圾文件
  • 原文地址:https://www.cnblogs.com/liuboren/p/14782914.html
Copyright © 2020-2023  润新知