一次資料庫洩露的解決經歷

經典雞翅發表於2020-08-22

前言

最近用了公司某框架,部署到現場後,現場運維開始維護現場資料,在不斷操作的過程中,系統崩潰,檢視後臺日誌,druid連線池已經獲取不到連線。於是開始了排查之旅。在此記錄。

排查開始

首先後臺的報錯是這樣的。

exception=org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 10, maxActive 10

第一反應maxActive設定的數量太少了。於是改為100。重新啟動,並再次操作大量資料。發現過了一段時間100個也滿了。

此時問題不簡單了。看來是有程式碼用了程式連線後,沒有釋放。

接下來開始確認原因到底是不是有沒有釋放。

在專案中使用的druid連線池。druid連線池是自帶圖形化監控工具的。於是開始在專案中配置,啟動druid連線池。

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DruidConfig {

   @Bean
   public ServletRegistrationBean statViewServlet(){
      ServletRegistrationBean srb =
         new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
      //設定控制檯管理使用者
      srb.addInitParameter("loginUsername","root");
      srb.addInitParameter("loginPassword","root");
      //是否可以重置資料
      srb.addInitParameter("resetEnable","false");
      return srb;
   }

   /**
    * 註冊FilterRegistrationBean
    * @return
    */
   @Bean
   public FilterRegistrationBean druidStatFilter() {
      FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());
      //新增過濾規則.
      bean.addUrlPatterns("/*");
      //新增不需要忽略的格式資訊.
      bean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
      return bean;
   }
}

重新啟動後,訪問:localhost:8081/druid。這個路徑有人大概會懷疑如果我是通過閘道器管理的微服務框架,需要通過閘道器轉發訪問嗎?其實大可不必,通過閘道器也可以,也是跳轉到直接訪問的地址。

進入訪問地址,跳轉到如下的登入介面。

輸入剛才在程式碼中配置的使用者名稱和密碼,則可以成功進入。

我們此時要點選資料來源,去關注建立的邏輯連線數和關閉的邏輯連線數,關鍵的指標在這裡,如果連線池的開啟和關閉是正常的,那麼二者的值應該是相等的。

再看看此時的活躍連線數

為0,此時正常,因為還沒有進行操作。

接下來開始對之前的操作進行復現,鎖定具體的操作。重複之前現場運維所做操作。

通過不斷的點選功能,縮小功能範圍,最終發現,只要點選左側樹,就會造成邏輯開啟連線和邏輯關閉次數不一致。

活躍連線數也到了二者之差。等了幾分鐘,仍然是這個情況。那麼實錘了 這裡的程式碼有問題,連線應該沒有釋放。那麼程式碼那麼多,該如何發現具體程式碼的位置呢。

接下來配置druid的abandon策略。通過abandon可以強制回收資料庫的連線。而活躍的連線被回收則會列印堆疊資訊,這是就知道是哪裡的sql程式碼沒有釋放了。

配置如下:

spring:
  datasource:
    druid:
      remove-abandoned: true
      remove-abandoned-timeout: 30
      log-abandoned: true

重啟專案,這個時候durid的監控活躍連線數的功能就可以看到程式碼資訊。

我們點選如下位置:

就會彈出上圖的堆疊資訊。打馬賽克的地方就是程式碼的詳細位置,會標記出來。開發人員去響應的類找到相應程式碼檢視即可。

經過檢視程式碼發現,程式碼的連線釋放存在問題。是自己封裝的sql查詢類。改為mybatis的寫法後,問題解決。

相關文章