一次數據庫泄露的解決經歷
- 2020 年 8 月 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的寫法後,問題解決。