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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/302896.html
標籤:Java
