Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接
1、背景
在當(dāng)前這個(gè)快速開發(fā)的環(huán)境下,很多時(shí)候我們的應(yīng)用都是測(cè)試好好的,正式環(huán)境并發(fā)一高就一團(tuán)糟。不了解并發(fā)相關(guān)參數(shù),看不懂壓測(cè)報(bào)告,是很多程序猿的基本狀態(tài)。本文重點(diǎn)分享長(zhǎng)事務(wù)以及長(zhǎng)連接導(dǎo)致的并發(fā)排查和優(yōu)化思路和示例。
長(zhǎng)事務(wù)會(huì)導(dǎo)致長(zhǎng)連接,長(zhǎng)連接未必是因?yàn)殚L(zhǎng)事務(wù),因果關(guān)系先搞清楚。
主要相關(guān)技術(shù):
- SpringBoot: 2.5.12
- mybatis-spring-boot-starter: 2.1.2
- druid-spring-boot-starter: 1.2.9
- mysql-connector-java: 8.0+
- tomcat: 9.0.54
2、主要參數(shù)釋義:
2.1 tomcat主要并發(fā)參數(shù)釋義
server: port: 8080 compression: enabled: true tomcat: accept-count: 511 max-connections: 8192 threads: max: 200 basedir: /u01/app/base/logs/tomcat connection-timeout: 60000 keep-alive-timeout: 60000
**threads.max:**表示服務(wù)器最大有多少個(gè)線程處理請(qǐng)求,默認(rèn)200,實(shí)際上這個(gè)參數(shù)超過服務(wù)器核心數(shù)太多反而會(huì)降低服務(wù)器cpu處理速度,對(duì)于計(jì)算密集型和IO密集型應(yīng)分開考慮設(shè)置該參數(shù)。
**max-connections:**表示服務(wù)器與客戶端可以建立多少個(gè)連接數(shù),即持有的連接數(shù)。tomcat缺省是8192個(gè)連接數(shù),cpu未必有時(shí)間給你處理,但是可以保持連接。這個(gè)參數(shù)是客戶感知型參數(shù)。
accept-count: 與服務(wù)器內(nèi)核相關(guān),是客戶端傳入給服務(wù)器內(nèi)核,請(qǐng)求的backlog值,該值與服務(wù)器內(nèi)核參數(shù)net.core.somaxconn
取小后的值為最終起效的TCP內(nèi)核全隊(duì)列值。它表示在max-connections值達(dá)到預(yù)設(shè)的值后,服務(wù)器內(nèi)核還能建立的連接數(shù),這個(gè)連接保存在內(nèi)核,還未被上層應(yīng)用(tomcat)取走。該值在tomcat中默認(rèn)是100,在Centos7.x版本中內(nèi)核net.core.somaxconn
是128。如果超過max-connections和accept-count總和,新的連接會(huì)被拒絕,即直接拒絕服務(wù)(直接返回connection refused)。
查看CentOS的net.core.somaxconn參數(shù):sysctl -a|grep net.core.somaxconn
2.2 數(shù)據(jù)庫連接池參數(shù)
datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver #連接池屬性 initial-size: 5 max-active: 140 min-idle: 10 # 配置獲取連接等待超時(shí)的時(shí)間 max-wait: 30000 connect-properties.slowSqlMillis: 2000
max-active: 數(shù)據(jù)庫連接池?cái)?shù)據(jù)連接最大數(shù)量,即連接池物理打開數(shù)據(jù)庫的最大數(shù)量。這個(gè)參數(shù)一般開發(fā)人員都會(huì)錯(cuò)誤的設(shè)置,首先這個(gè)值不是越大越好,最起碼它得小于數(shù)據(jù)庫本身配置的最大連接數(shù),如果超過后再向數(shù)據(jù)庫發(fā)起連接,就會(huì)在數(shù)據(jù)庫層面拋出類似"too many connection"的錯(cuò)誤。mysql數(shù)據(jù)庫默認(rèn)最大連接數(shù)為151。一般配置數(shù)據(jù)庫連接池應(yīng)用組件的時(shí)候,不要超過這個(gè)數(shù),并且需要留一部分連接數(shù)給維護(hù)人員使用。
2.3 數(shù)據(jù)庫連接數(shù)
上文2.2已經(jīng)提到數(shù)據(jù)庫連接數(shù),它決定了數(shù)據(jù)庫支持的最大并發(fā)數(shù)。
查看mysql的最大連接數(shù):
show variables like '%max_connections%';
查看mysql目前的連接數(shù):
show global status like 'Max_used_connections';
如果你的應(yīng)用配置連接數(shù)超過數(shù)據(jù)庫預(yù)設(shè)的最大數(shù),并且客戶端不斷并發(fā)的發(fā)起數(shù)據(jù)庫連接,連接池?cái)?shù)量就會(huì)不斷創(chuàng)建與數(shù)據(jù)庫的物理連接,如果該連接是各種原因?qū)е碌臄?shù)據(jù)庫長(zhǎng)連接(例如:長(zhǎng)事務(wù)),那么一旦超過數(shù)據(jù)庫的最大值,應(yīng)用就會(huì)報(bào)連接數(shù)太多的錯(cuò)誤。
3、測(cè)試程序
兩個(gè)對(duì)外暴露的url:
- 5000毫秒的長(zhǎng)連接操作
- 100毫秒的短連接操作
Controller:
@GetMapping("/slowGetAll") public Result<Object> slowGetAll(){ return Result.ok(testUserService.queryAll()); } @GetMapping("/fastGetAll") public Result<Object> fastGetAll(){ return Result.ok(testUserService.fastGetAll()); }
service: 向數(shù)據(jù)庫里插入兩個(gè)用戶,并查詢最后一個(gè)用戶的信息,在兩次insert操作中間加入一個(gè)耗時(shí)操作。
@Transactional(rollbackFor = Exception.class) @Override public List<TestUser> fastGetAll() { TestUser user1 = getTestUser("李四", "jerry"); this.testUserDao.insert(user1); slowMethod(customProperties.getFastMillis()); TestUser user2 = getTestUser("張三", "tom"); this.testUserDao.insert(user2); return this.testUserDao.queryByBlurry(user2); } @Transactional(rollbackFor = Exception.class) @Override public List<TestUser> queryAll() { TestUser user1 = getTestUser("王五", "jack"); this.testUserDao.insert(user1); slowMethod(customProperties.getSlowMillis()); TestUser user2 = getTestUser("趙柳", "amy"); this.testUserDao.insert(user2); return this.testUserDao.queryByBlurry(user2); } private void slowMethod(int milliseconds){ try { int i = globalCount.incrementAndGet(); System.out.println("slowMethod start -->"+i); Thread.sleep(milliseconds); System.out.println("slowMethod end -->"+i); } catch (InterruptedException e) { e.printStackTrace(); } }
注意,這里的長(zhǎng)連接使用使用Thread.sleep實(shí)現(xiàn),不是真正的數(shù)據(jù)庫長(zhǎng)事務(wù)。
4、jmeter測(cè)試
開啟400線程,測(cè)試10輪,分兩組:
- 快速處理的短連接4000次。
- 慢速處理的長(zhǎng)連接4000次。
4.1、快速組
druid連接池主要結(jié)果分析:
指標(biāo) | 值 | 解釋 |
---|---|---|
事務(wù)時(shí)間分布 | 0,0,3735,265,0,0,0 | 事務(wù)運(yùn)行時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s] |
連接持有時(shí)間分布 | 0,0,0,3583,417,0,0,0 | 連接持有時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s] |
在JMeter中的吞吐量為:305.4/second
4.2、慢速組
druid連接池主要結(jié)果分析:
指標(biāo) | 值 | 解釋 |
---|---|---|
事務(wù)時(shí)間分布 | 0,0,3599,401,0,0,0 | 事務(wù)運(yùn)行時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s] |
連接持有時(shí)間分布 | 0,0,0,0,4000,0,0,0 | 連接持有時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s] |
在JMeter中的吞吐量為:26.3/second
4.3、對(duì)照分析
事務(wù)時(shí)間分布:非數(shù)據(jù)庫操作的耗時(shí)對(duì)事務(wù)時(shí)間分布是沒有影響的,快速組和慢速組分布區(qū)間基本相同。
連接持有時(shí)間分布:快速組大約90%的時(shí)間分布在100ms-1s
,慢速組100%分布在1-10 s
,符合預(yù)期的設(shè)置。
吞吐量:快速組比慢速組快了接近10倍。
小小結(jié)論:在慢速組中,雖然事務(wù)的分別時(shí)間較短,但是發(fā)起該事務(wù)的連接一直沒有被釋放,導(dǎo)致并發(fā)能力斷崖式下降。
5、問題與優(yōu)化
5.1、問題
在本次測(cè)試中,影響應(yīng)用并發(fā)性能主要體現(xiàn)在長(zhǎng)連接持有時(shí)間,當(dāng)服務(wù)器處理某個(gè)請(qǐng)求耗時(shí)較長(zhǎng)會(huì)導(dǎo)致并發(fā)能力直線下降,這個(gè)耗時(shí)可能會(huì)因?yàn)閿?shù)據(jù)庫長(zhǎng)事務(wù)、長(zhǎng)計(jì)算、或發(fā)起對(duì)外慢速同步的API請(qǐng)求等等原因?qū)е隆?/p>
5.2 、排查
通過druid monitor監(jiān)控可以查看很多與數(shù)據(jù)庫連接的參數(shù)和實(shí)際發(fā)生狀態(tài),本例中,主要需要找到事務(wù)時(shí)間分布和連接持有時(shí)間即可初步定位問題,然后通過SQL監(jiān)控找到發(fā)生的SQL語句逐步排查定位到JAVA代碼塊,找到代碼塊一般都能分析出實(shí)際的問題。
5.3、核心
在整個(gè)應(yīng)用生態(tài)中,最寶貴的資源就是數(shù)據(jù)庫連接,數(shù)據(jù)庫相關(guān)業(yè)務(wù)密集的系統(tǒng)中,首先需要保證盡可能少的持有數(shù)據(jù)庫連接。所以才催生出數(shù)據(jù)庫連接池這些技術(shù)。第二寶貴的是磁盤IO,所有對(duì)磁盤IO的操作盡可能少,所以催生出數(shù)據(jù)庫索引、緩存等技術(shù),對(duì)于需要直接操作磁盤IO的計(jì)算來說,能用順序讀或?qū)懀筒灰秒S機(jī)讀或?qū)憽?/p>
5.4、調(diào)優(yōu)
優(yōu)化并發(fā)需從幾個(gè)方面出發(fā):
- 服務(wù)器本身的參數(shù),比如CPU核數(shù),高性能磁盤位置等,通過服務(wù)器參數(shù)對(duì)應(yīng)設(shè)置前文所述的幾個(gè)應(yīng)用參數(shù),比如服務(wù)器CPU核心數(shù)為16個(gè),一般通用業(yè)務(wù)應(yīng)用下,缺省的200個(gè)線程已經(jīng)足夠。
- 一旦確認(rèn)服務(wù)器配置等基本的參數(shù),并拍腦袋設(shè)置了一些應(yīng)用參數(shù)后,就需要啟動(dòng)壓測(cè)測(cè)試各參數(shù)配比下最好的服務(wù)器和應(yīng)用性能。
- 確認(rèn)你的業(yè)務(wù)系統(tǒng)是計(jì)算密集型還是IO密集型,針對(duì)性的編寫測(cè)試用例。
- 最后確認(rèn)業(yè)務(wù)系統(tǒng)乃至服務(wù)器的性能瓶頸鏈,輸出整體性能報(bào)告。在實(shí)際生產(chǎn)中,一旦檢測(cè)到實(shí)際并發(fā)與性能報(bào)告相差太大就可以啟動(dòng)排查程序。
6、優(yōu)化實(shí)驗(yàn)
6.1 手動(dòng)事務(wù)
代碼優(yōu)化背景和目標(biāo):
- 首先長(zhǎng)連接的事實(shí)不可改變,因?yàn)橛锌赡苓@個(gè)計(jì)算或調(diào)用就是需要耗費(fèi)5秒的時(shí)間,如果能減少,則屬于另外的優(yōu)化邏輯;
- 在這個(gè)背景下,我們改善的目標(biāo)是減少數(shù)據(jù)庫連接的持有時(shí)間;
- 從實(shí)例代碼里,真正需要事務(wù)的操作是兩個(gè)insert,slowMethod和query查詢都不需要,所以啟動(dòng)手動(dòng)事務(wù)就可以減少數(shù)據(jù)庫連接的持有時(shí)間。
- 使用Springboot的手動(dòng)事務(wù)模板。
6.2、優(yōu)化第一組測(cè)試
代碼優(yōu)化如下:
@Resource private TransactionTemplate transactionTemplate; @Override public List<TestUser> optimizedGetAll() { slowMethod(customProperties.getSlowMillis()); TestUser user1 = getTestUser("王五", "jack"); TestUser user2 = getTestUser("趙柳", "amy"); transactionTemplate.execute(status -> { this.testUserDao.insert(user1); this.testUserDao.insert(user2); return Boolean.TRUE; }); return this.testUserDao.queryByBlurry(user2); }
druid連接池主要結(jié)果分析:
指標(biāo) | 值 | 解釋 |
---|---|---|
事務(wù)時(shí)間分布 | 0,0,2295,1626,79,0,0 | 事務(wù)運(yùn)行時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s] |
連接持有時(shí)間分布 | 2307,1041,2735,1838,79,0,0,0 | 連接持有時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s] |
在JMeter中的吞吐量為:37.3/second
測(cè)試分析:
- 首先連接持有時(shí)間大幅度下降,原先慢速組100%的樣本數(shù)據(jù)在
1-10 s
區(qū)間,現(xiàn)在改區(qū)間只有0.98%的數(shù)據(jù)。 - 我們發(fā)現(xiàn)連接持有時(shí)間分布樣本數(shù)據(jù)總和是8000,而不是原本慢速組的4000,并且在
0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s
都有分布,說明一個(gè)問題,query查詢單獨(dú)占用了數(shù)據(jù)庫連接。 - 我們知道數(shù)據(jù)庫連接是寶貴資源,query單獨(dú)占用連接是否有問題?從上面的測(cè)試結(jié)果分析,query雖然單獨(dú)占用資源,但是時(shí)間很短,并且也是復(fù)用了連接池的資源,相當(dāng)于把長(zhǎng)事務(wù)分?jǐn)偟蕉滩僮髦小J欠襁€有更優(yōu)配置呢?
- 所以我們還需要做一組測(cè)試,把query放入事務(wù)中試試看。本次連接持有時(shí)間分布總樣本中位數(shù)參考值是 1458262 ms。
- 最后,吞吐量有所提升,總體上這個(gè)優(yōu)化方案是有效果的。
6.3、優(yōu)化第二組測(cè)試
代碼優(yōu)化如下:
@Override public List<TestUser> optimizedGetAll2() { slowMethod(customProperties.getSlowMillis()); TestUser user1 = getTestUser("王五", "jack"); TestUser user2 = getTestUser("趙柳", "amy"); return transactionTemplate.execute(new TransactionCallback<List<TestUser>>() { @Override public List<TestUser> doInTransaction(TransactionStatus status) { testUserDao.insert(user1); testUserDao.insert(user2); return testUserDao.queryByBlurry(user2); } }); }
druid連接池主要結(jié)果分析:
指標(biāo) | 值 | 解釋 |
---|---|---|
事務(wù)時(shí)間分布 | 0,0,3778,222,0,0,0 | 事務(wù)運(yùn)行時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s] |
連接持有時(shí)間分布 | 0,0,2278,1611,111,0,0,0 | 連接持有時(shí)間分布,分布區(qū)間為[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s] |
在JMeter中的吞吐量為:38.0/second
測(cè)試分析:
- 對(duì)比一二組,首先事務(wù)分布時(shí)間是改善的,但是并不明顯;
- 本次連接持有時(shí)間分布總樣本中位數(shù)參考值是 1474400 ms,對(duì)比一二組,第二組比第一組多耗費(fèi) 16138 ms;
- 在該樣本的情況下,第一組實(shí)際持有連接的時(shí)間更短,壓力更為平均。
- 將不必要的query放入事務(wù)本身就是不推薦,本示例中query查詢的數(shù)據(jù)量較小,如果數(shù)據(jù)量較大,從理論上分析,會(huì)明顯影響事務(wù)的提交。
- 吞吐量基本無變化,這個(gè)是符合預(yù)期的。
- 所以這次優(yōu)化,推薦使用第一組的方式。
7、總結(jié)
- 在系統(tǒng)初次上線前,可以考慮編寫測(cè)試用例用壓測(cè)的方式拿到服務(wù)器的性能指標(biāo)。
- 在系統(tǒng)完成開發(fā)后,可以考慮對(duì)簡(jiǎn)單、中等、高復(fù)雜度的API分別進(jìn)行壓測(cè)摸清API的性能體現(xiàn),以及對(duì)比服務(wù)器性能指標(biāo),有可能在上線前就能排查出問題。
- 在系統(tǒng)上線后,如果遇到應(yīng)用性能下降或并發(fā)問題,可以通過觀察連接池和SQL分析定位大致的java代碼塊,解決問題最終還是需要針對(duì)性編寫測(cè)試用例再次復(fù)盤測(cè)試。
- 大部分情況下,系統(tǒng)缺省的參數(shù)都?jí)蚰闶褂茫魏我淮螀?shù)優(yōu)化調(diào)整都應(yīng)該有理論支撐和實(shí)踐證明,云調(diào)參張口就來誰都會(huì)。
- 并發(fā)調(diào)優(yōu)一定要抓住關(guān)鍵目標(biāo),找到核心資源,分析并發(fā)瓶頸鏈路。
- 數(shù)據(jù)庫連接池還有很多參數(shù)是可以幫助調(diào)優(yōu)分析的,本文沒有介紹,jmeter的壓測(cè)的方式也是多種多樣,應(yīng)根據(jù)不同的場(chǎng)景調(diào)整。
- 并發(fā)調(diào)優(yōu)是一個(gè)復(fù)雜的過程,從應(yīng)用所有的關(guān)聯(lián)點(diǎn)都可能出現(xiàn)問題,本文只是從開發(fā)角度,針對(duì)請(qǐng)求長(zhǎng)連接以及數(shù)據(jù)庫長(zhǎng)事務(wù)做了簡(jiǎn)單的分析和推測(cè)。
到此這篇關(guān)于Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接的文章就介紹到這了,更多相關(guān)Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java ArrayList與LinkedList及HashMap容器的用法區(qū)別
這篇文章主要介紹了Java ArrayList與LinkedList及HashMap容器的用法區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-07-07springcloud項(xiàng)目快速開始起始模板的實(shí)現(xiàn)
本文主要介紹了springcloud項(xiàng)目快速開始起始模板思路的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12maven倉庫中心mirrors配置多個(gè)下載中心(執(zhí)行最快的鏡像)
這篇文章主要介紹了maven倉庫中心mirrors配置多個(gè)下載中心(執(zhí)行最快的鏡像),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07