記一次druid連接池配置問題引發服務假死的定位、分析、解決過程

 

一、前言

        記錄一次服務假死的整個排查過程,服務基礎爲spring boot + druid + 多數據源切換,在請求過多(尤其是長事務請求)時,服務出現請求無響應的狀況,之前未完結的查詢也沒有任何返回結果。

二、定位問題原因

        問題出現時,表現如下圖,後臺無任何報錯,sql語句戛然而止,後續的查詢被中斷。這時如果再次發起某個請求,後臺服務處於大部分時間不能收到新的請求的狀態,或者偶爾可收到請求但不會執行crud。經過一段時間後,日誌輸出了session校驗的內容,此時我推測服務並不是真的宕機,而是處於假死狀態。

   

    druid配置如下

        

        看了下配置文件,最大連接池數量設置爲100,但重現問題的過程不需要特別多的請求,穩妥起見,將最大數量改爲200,問題沒有解決,初步推測不是因爲請求達到了連接池上限。

        CPU和內存均無異常。

         期間還優化過業務邏輯,甚至將同步請求改爲異步請求,都無濟於事。一籌莫展之際,想起了druid自帶的監控功能。打開監控頁面,找到了一處令我懷疑的地方。數據源中的邏輯打開和邏輯關閉次數起初是一致的,隨着查詢次數增多,邏輯關閉次數小於邏輯打開次數,於是我懷疑數據庫連接池出現了泄露的情況,根據URI監控中顯示的情況,jdbc出錯數剛好等於邏輯打開與邏輯關閉次數的差,也就是說,很有可能由於jdbc出錯導致數據庫連接池未正確關閉。

(圖片爲部分截圖,下面還有個1沒有截到) 

        按着這個思路,對測試部分代碼進行了排查,無果。後來根據druid官方的文檔,找到了下面這段話。

         

        從這段話中可以看出,判斷是否是泄露應該在URI監控中,點擊URI進入詳情頁面,查看打開和關閉的數量是否相等。於是我在邏輯連接打開和邏輯連接關閉次數有巨大差異的情況下,對每一個URI都進行了覈查,所有URI詳情中,連接池獲取連接次數都是等於連接池關閉連接次數的,理論上證明數據庫連接池並無泄露。

        前面圖中druid的文檔中還提到了removeAbandoned等三個屬性用以檢測數據庫連接池泄露,於是我將這三個屬性寫在了yml裏。

        一通華麗麗的操作下來,服務依舊被玩壞,然後打開了druid的監控,找到了數據源頁面中的ActiveConnectionStackTrace。點開,沒數據???控制檯也沒輸出日誌??? 網上找了一些文章,然後大膽的懷疑是不是druid的配置沒生效?再看一下druid運行時的數據源,果不其然。初始化連接大小、最小空閒連接數、最大連接數、超時時間等等等等,除了數據庫指向是生效的,其他配置使用的都是缺省值。所以我即使把最大數量從100改成了200,依然是沒幾個請求就爆炸了。

 三、解決方案

        下面開始着手解決配置沒生效的問題。由於項目是多數據源切換,於是找到了這個配置文件,嘗試着改造了一下,將yml中的配置set到了DruidDataSource對象中。

      

(下圖僅展示了與本文有關的內容) 

@Configuration
public class DruidConfig {

	@Value("${spring.datasource.druid.initial-size}")
	private int initialSize;

	@Value("${spring.datasource.druid.min-idle}")
	private int minIdle;

	@Value("${spring.datasource.druid.max-active}")
	private int maxActive;

	@Value("${spring.datasource.druid.max-wait}")
	private int maxWait;

	@Value("${spring.datasource.druid.pool-prepared-statements}")
	private boolean poolPreparedStatements;

	@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
	private int maxPoolPreparedStatementPerConnectionSize;

	@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
	private int timeBetweenEvictionRunsMillis;

	@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
	private int minEvictableIdleTimeMillis;

	@Value("${spring.datasource.druid.test-while-idle}")
	private boolean testWhileIdle;

	@Value("${spring.datasource.druid.test-on-borrow}")
	private boolean testOnBorrow;

	@Value("${spring.datasource.druid.test-on-return}")
	private boolean testOnReturn;

	@Value("${spring.datasource.druid.remove-abandoned}")
	private boolean removeAbandoned;

	@Value("${spring.datasource.druid.remove-abandoned-timeout}")
	private int removeAbandonedTimeout;

	@Value("${spring.datasource.druid.log-abandoned}")
	private boolean logAbandoned;

	/**
	 * 設置數據庫連接池
	 * 
	 * @author sunbin
	 * @since 2020年4月21日
	 * @version 2020年4月21日
	 * @param dataSource
	 */
	private void setDruidDataSource(DruidDataSource dataSource) {
		dataSource.setInitialSize(initialSize);
		dataSource.setMinIdle(minIdle);
		dataSource.setMaxActive(maxActive);
		dataSource.setMaxWait(maxWait);
		dataSource.setPoolPreparedStatements(poolPreparedStatements);
		dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
		dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
		dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
		dataSource.setTestWhileIdle(testWhileIdle);
		dataSource.setTestOnBorrow(testOnBorrow);
		dataSource.setTestOnReturn(testOnReturn);
		dataSource.setRemoveAbandoned(removeAbandoned);
		dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout);
		dataSource.setLogAbandoned(logAbandoned);
	}

	/**
	 * 默認數據源
	 * 
	 * @author 89390
	 * @since 2019年4月15日
	 * @version 2019年4月15日
	 * @return
	 */
	@Bean
	@ConfigurationProperties("spring.datasource.druid.master")
	public DataSource masterDataSource() {
		DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
		setDruidDataSource(dataSource);
		return dataSource;
	}
}

        重啓服務,查看druid monitor,與yml中配置的一致了,即證明此時配置已經生效,再次測試,服務假死問題解決。

四、分析 

        項目中,spring boot版本爲2.0.3.RELEASE

        druid版本爲1.1.10

        

        考慮到版本等問題,雖然我們在yml中配置了druid連接池的其它屬性,但是不會生效。因爲默認是使用的java.sql.Datasource的類來獲取屬性的,有些屬性datasource沒有。如果我們想讓配置生效,需要手動創建Druid的配置文件。由於項目中多數據源的功能,已經有了Druid的配置文件,所以無需新建,適當修改即可。

五、參考 

https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B

https://www.cnblogs.com/SimpleWu/p/10049825.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章