記一次解決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,解決問題的過程中遇到了很多的問題,不斷的翻源碼找到合適的方法,問題得到了解決.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM