一次資料庫洩露的解決經歷
前言
最近用了公司某框架,部署到現場後,現場運維開始維護現場資料,在不斷操作的過程中,系統崩潰,檢視後臺日誌,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的寫法後,問題解決。