資源中心——連接池調優


1、背景

前一段時間觀察了一下資源中心CPU的利用率,入下圖

CPU峰值利用率在10%左右,有點資源浪費,所以進行了縮容。在節省了30%的硬件資源之后,服務大部分指標正常,但是超時量有點增長,有原來的每天50以內,變到了如今的250以內。所以來看一波小小的優化。

首先對比下縮容前后的變化

  • 硬件資源減少
  • RPC工作線程減少
  • DB鏈接數減少

先看硬件資源縮容之后的監控如下:

最高值CPU40%以上,均值最大在20%左右,利用率並不是特別高。然后觀察網卡、內存硬盤等參數均沒有達到影響性能的程度。

2、方案

我們從剩下的兩點入手:

1、增加RPC的工作隊列數,說實話起到的效果並不大。

2、增加連接池的連接數。經過這兩條觀察超時量在40以內,效果更勝從前。

3、DBCP2原理

連接池本質上的原理和線程池的大體原理類似。我們用的是DPCP2,具體原理如下圖

核心是空閑隊列。連接池所有的核心工作都是圍繞這個隊列進行出隊和入隊的。整體上分為獲取連接、歸還連接、以及連接異步定時檢測三大模塊。

1)獲取連接,從空閑連接池中拿連接。獲取連接時會有對連接的有效性檢查以及連接泄漏檢查。這個兩個環節都是可配置的對應的參數為:testOnBorrow,removeAbandonedOnBorrow。testOnBorrow(連接有效性檢查)不建議開啟的原因:有效性驗證需要需要執行你的驗證SQL,會損耗性能,並且testWhileIdle開啟定期異步檢查就可以了。其次當從連接池中獲取不到連接時並且當前連接數小於最大連接數時(maxTotal),會自動創建一個連接;如果超過最大連接數會進行等待,如果超過了設置的等待的超時時間(maxWaitMillis)則拋出異常。這里注意的點是:只有大於最大空閑連接數時才會等待,空閑隊列為空時不會等待。

2)連接歸還。如果配置了testOnReturn,則在歸還連接時進行有效性檢查,同樣不建議開啟,浪費性能。其次如果空閑連接超過最大空閑連接數(maxIdle),則會銷毀改連接,同步進行真正的關閉連接。所以最大連接數的控制是由獲取連接和歸還連接兩個操作來控制

3) 連接異步定時檢測  。timeBetweenEvictionRunsMillis只有配置了這個參數才會進行定時檢測。

  • 空閑時間檢測,檢查連接是否超過最小空閑時間超過則需要清理。需要配置:minEvictableIdleTimeMillis(連接最小空閑時間)。除此之外還建議配置:numTestsPerEvictionRun,每次檢查連接個數,不配置的話就是所有空閑連接,DBCP2檢測空閑連接的做法是將要檢測的連接設置為檢測狀態,避免與獲取連接的線程沖突,所以如果是所有連接的話,那么在有一段時間內連接池將沒有連接可以用,將會不起效果,所以numTestsPerEvictionRun不宜配置過大。
  • 最小連接數(minIdle)的維護。和空閑連接數檢查是在一個線程中維護,需要配置softMinEvictableIdleTimeMillis,才會在連接超過最小連接數時,回收最小鏈接之外的連接。這個可以比minEvictableIdleTimeMillis小一些,這樣超過最小連接數的連接回收的會快一些。
  • 有效性檢查,需要配置testWhileIdle。如果配置了validationQuery,在連接有效性檢查的時候會執行該SQL,否則會用PING來檢查連接的有效性。
  • 連接泄漏檢測。當我們從連接池獲得了連接對象,但因為疏忽或其他原因沒有close,這個時候這個連接對象就是一個泄露資源。通過配置以下參數可以回收這部分對象。removeAbandonedOnBorrow=true在每次從連接池中獲取連接時盡心檢查。該參數為true並不是每次都會檢查需要:this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3,也就是說空閑連接數特別少,活躍連接數特別多並且接近最大連接數這就說明這時候有部分連接被持有並沒有釋放。removeAbandonedTimeout該參數是連接被使用久算超時,默認是300s。removeAbandonedOnMaintenance配置是否在定時任務中進行檢測。以上說的幾個檢測都是在同一個線程中進行。

4、核心參數設置方式

1)隊列策略選擇:用戶默認的就好,先進后出隊列(棧)。正常連接從對頭獲取和歸還。異步檢測任務從隊尾處理,減少兩者沖突。

2)maxTotal在DB允許的情況下盡可能的大。可以保證突增流量,有充足的連接可以處理請求。maxIdle,minIdle,initialSize這三個值建議設成一個值。保證核心連接數的穩定,減少在使用連接時創建連接的頻率。具體怎么設置呢?由於DBCP2沒有對應的監控,我寫了一下如下的簡單的監控。

@Component
public class PoolMonitor {

private static final Logger logger = LoggerFactory.getLogger(PoolMonitor.class);

@Autowired
Map<String, BasicDataSource> pools;

@PostConstruct
public void init() {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
monitoring();
}, 0, 1L, TimeUnit.SECONDS);
}

public void monitoring() {
Set<Map.Entry<String, BasicDataSource>> poolEntries = pools.entrySet();
for (Map.Entry<String, BasicDataSource> poolEntry : poolEntries) {
BasicDataSource dataSource = poolEntry.getValue();
int maxTotal = dataSource.getMaxTotal();
int numActive = dataSource.getNumActive();
int maxIdle = dataSource.getMaxIdle();
int numIdle = dataSource.getNumIdle();
logger.info("basic datasource pools monitoring:name {} maxTotal {} numActive {} maxIdle {} numIdle {}",
poolEntry.getKey(), maxTotal, numActive, maxIdle, numIdle);
}
}
}

maxIdle,minIdle,initialSize這個值的設置要保證:活躍連接數+當前空閑連接數=最小連接數(minIdle)就行。這樣就不會產生正常請求獲取連接時連接不夠從而創建連接的現象了。

3)numTestsPerEvictionRun這個值該怎么設置呢?numTestsPerEvictionRun,不要太大,最小值是采用如下的設置策略就行。mysql默認維護空閑連接的最大時間是8小時,超過這個時間就會斷開。所以安全的配置策略是:minEvictableIdleTimeMillis+timeBetweenEvictionRunsMillis +maxTotal(最大空閑連接數)/numTestsPerEvictionRun*timeBetweenEvictionRunsMillis<8*3600*1000。這樣設置能保證最壞的場景下都可檢測到每一個連接在8小時內都被檢測過。最壞的場景是:空閑連接數一下子達到了最大值,並且再也沒有被方位過,最小連接數維護的配置沒有開啟。

注意:  initialSize  連接池初始化的時候並不會創建連接,在第一次與DB交互的時候才會初始化連接。如介意服務啟動時請求較慢的服務的話,需要做好預熱,很簡單給每個庫調用一次數據庫查詢就行。

5、我們的設置

#連接最大總數
jdbc.maxTotal=200
#最大空閑連接數
jdbc.maxIdle=30
#最小空閑連接數 類似於線程池中的coreSize
jdbc.minIdle=30
#初始化連接數,在第一次getConnection中進行創建連接
jdbc.initialSize=30 #從連接池中獲取不到連接並且當前連接數超過最大空閑連接數時,等待對應的秒數后,再次從連接池中獲取連接,再次獲取不到則拋異常。默認無限等待。 jdbc.maxWaitMillis=500 #異步定時檢測連接的有效性
jdbc.testWhileIdle=true#連接池檢測time的定期執行時間
jdbc.timeBetweenEvictionRunsMillis=600000
#每次檢測幾個空閑連接,不能太大,否則會影響從連接池中取連接
jdbc.numTestsPerEvictionRun=10

#空閑連接超過最小連接數時,超過最小連接數這部分連接的檢測連接的超時時間
jdbc.softMinEvictableIdleTimeMillis=300000

#連接最小空閑時間,最大連接空閑時間才會清理無用連接,不會根據minIdle進行清理,如果請求量大的話可能空閑連接會超過20個jdbc.minEvictableIdleTimeMillis=1800000

#開啟連接泄漏檢測,每次從從連接池中取連接時檢測連接是否為泄漏連接,進行檢測的條件是:(getNumIdle() < 2) and (getNumActive() > (getMaxActive() - 3))空閑連接較小或者活躍連接數時才會檢測
jdbc.removeAbandonedOnBorrow=true
#在空閑連接回收器中進行檢測
jdbc.removeAbandonedOnMaintenance=true
#連接泄漏中,判定連接為泄漏連接的時長
jdbc.removeAbandonedTimeout=20
#每次從連接池中去連接時是否進行有效性檢測
jdbc.testOnBorrow=false

 

 參考鏈接 

https://www.cnblogs.com/ZhangZiSheng001/p/12003922.html

https://zhuanlan.zhihu.com/p/383694802


免責聲明!

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



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