最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接

 更新時(shí)間:2022年05月09日 11:08:31   作者:我是屬車的  
這篇文章主要介紹了Springboot并發(fā)調(diào)優(yōu)之大事務(wù)和長(zhǎng)連接,重點(diǎn)分享長(zhǎng)事務(wù)以及長(zhǎng)連接導(dǎo)致的并發(fā)排查和優(yōu)化思路和示例,具有一定的參考價(jià)值,感興趣的可以了解一下

1、背景

在當(dāng)前這個(gè)快速開(kāi)發(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ù)超過(guò)服務(wù)器核心數(shù)太多反而會(huì)降低服務(wù)器cpu處理速度,對(duì)于計(jì)算密集型和IO密集型應(yīng)分開(kāi)考慮設(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。如果超過(guò)max-connections和accept-count總和,新的連接會(huì)被拒絕,即直接拒絕服務(wù)(直接返回connection refused)。

查看CentOS的net.core.somaxconn參數(shù):sysctl -a|grep net.core.somaxconn

2.2 數(shù)據(jù)庫(kù)連接池參數(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ù)庫(kù)連接池?cái)?shù)據(jù)連接最大數(shù)量,即連接池物理打開(kāi)數(shù)據(jù)庫(kù)的最大數(shù)量。這個(gè)參數(shù)一般開(kāi)發(fā)人員都會(huì)錯(cuò)誤的設(shè)置,首先這個(gè)值不是越大越好,最起碼它得小于數(shù)據(jù)庫(kù)本身配置的最大連接數(shù),如果超過(guò)后再向數(shù)據(jù)庫(kù)發(fā)起連接,就會(huì)在數(shù)據(jù)庫(kù)層面拋出類似"too many connection"的錯(cuò)誤。mysql數(shù)據(jù)庫(kù)默認(rèn)最大連接數(shù)為151。一般配置數(shù)據(jù)庫(kù)連接池應(yīng)用組件的時(shí)候,不要超過(guò)這個(gè)數(shù),并且需要留一部分連接數(shù)給維護(hù)人員使用。

2.3 數(shù)據(jù)庫(kù)連接數(shù)

上文2.2已經(jīng)提到數(shù)據(jù)庫(kù)連接數(shù),它決定了數(shù)據(jù)庫(kù)支持的最大并發(fā)數(shù)。

查看mysql的最大連接數(shù):

show variables like '%max_connections%';

查看mysql目前的連接數(shù):

show global status like 'Max_used_connections';

如果你的應(yīng)用配置連接數(shù)超過(guò)數(shù)據(jù)庫(kù)預(yù)設(shè)的最大數(shù),并且客戶端不斷并發(fā)的發(fā)起數(shù)據(jù)庫(kù)連接,連接池?cái)?shù)量就會(huì)不斷創(chuàng)建與數(shù)據(jù)庫(kù)的物理連接,如果該連接是各種原因?qū)е碌臄?shù)據(jù)庫(kù)長(zhǎng)連接(例如:長(zhǎng)事務(wù)),那么一旦超過(guò)數(shù)據(jù)庫(kù)的最大值,應(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ù)庫(kù)里插入兩個(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ù)庫(kù)長(zhǎng)事務(wù)。

4、jmeter測(cè)試

開(kāi)啟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ù)庫(kù)操作的耗時(shí)對(duì)事務(wù)時(shí)間分布是沒(méi)有影響的,快速組和慢速組分布區(qū)間基本相同。

連接持有時(shí)間分布:快速組大約90%的時(shí)間分布在100ms-1s,慢速組100%分布在1-10 s,符合預(yù)期的設(shè)置。

吞吐量:快速組比慢速組快了接近10倍。

小小結(jié)論:在慢速組中,雖然事務(wù)的分別時(shí)間較短,但是發(fā)起該事務(wù)的連接一直沒(méi)有被釋放,導(dǎo)致并發(fā)能力斷崖式下降。

5、問(wèn)題與優(yōu)化

5.1、問(wèn)題

在本次測(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ù)庫(kù)長(zhǎng)事務(wù)、長(zhǎng)計(jì)算、或發(fā)起對(duì)外慢速同步的API請(qǐng)求等等原因?qū)е隆?/p>

5.2 、排查

通過(guò)druid monitor監(jiān)控可以查看很多與數(shù)據(jù)庫(kù)連接的參數(shù)和實(shí)際發(fā)生狀態(tài),本例中,主要需要找到事務(wù)時(shí)間分布和連接持有時(shí)間即可初步定位問(wèn)題,然后通過(guò)SQL監(jiān)控找到發(fā)生的SQL語(yǔ)句逐步排查定位到JAVA代碼塊,找到代碼塊一般都能分析出實(shí)際的問(wèn)題。

5.3、核心

在整個(gè)應(yīng)用生態(tài)中,最寶貴的資源就是數(shù)據(jù)庫(kù)連接,數(shù)據(jù)庫(kù)相關(guān)業(yè)務(wù)密集的系統(tǒng)中,首先需要保證盡可能少的持有數(shù)據(jù)庫(kù)連接。所以才催生出數(shù)據(jù)庫(kù)連接池這些技術(shù)。第二寶貴的是磁盤IO,所有對(duì)磁盤IO的操作盡可能少,所以催生出數(shù)據(jù)庫(kù)索引、緩存等技術(shù),對(duì)于需要直接操作磁盤IO的計(jì)算來(lái)說(shuō),能用順序讀或?qū)?,就不要用隨機(jī)讀或?qū)憽?/p>

5.4、調(diào)優(yōu)

優(yōu)化并發(fā)需從幾個(gè)方面出發(fā):

  • 服務(wù)器本身的參數(shù),比如CPU核數(shù),高性能磁盤位置等,通過(guò)服務(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ì)性的編寫(xiě)測(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ù)庫(kù)連接的持有時(shí)間;
  • 從實(shí)例代碼里,真正需要事務(wù)的操作是兩個(gè)insert,slowMethod和query查詢都不需要,所以啟動(dòng)手動(dòng)事務(wù)就可以減少數(shù)據(jù)庫(kù)連接的持有時(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都有分布,說(shuō)明一個(gè)問(wèn)題,query查詢單獨(dú)占用了數(shù)據(jù)庫(kù)連接。
  • 我們知道數(shù)據(jù)庫(kù)連接是寶貴資源,query單獨(dú)占用連接是否有問(wèn)題?從上面的測(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ù)的提交。
  • 吞吐量基本無(wú)變化,這個(gè)是符合預(yù)期的。
  • 所以這次優(yōu)化,推薦使用第一組的方式。

7、總結(jié)

  • 在系統(tǒng)初次上線前,可以考慮編寫(xiě)測(cè)試用例用壓測(cè)的方式拿到服務(wù)器的性能指標(biāo)。
  • 在系統(tǒng)完成開(kāi)發(fā)后,可以考慮對(duì)簡(jiǎn)單、中等、高復(fù)雜度的API分別進(jìn)行壓測(cè)摸清API的性能體現(xiàn),以及對(duì)比服務(wù)器性能指標(biāo),有可能在上線前就能排查出問(wèn)題。
  • 在系統(tǒng)上線后,如果遇到應(yīng)用性能下降或并發(fā)問(wèn)題,可以通過(guò)觀察連接池和SQL分析定位大致的java代碼塊,解決問(wèn)題最終還是需要針對(duì)性編寫(xiě)測(cè)試用例再次復(fù)盤測(cè)試。
  • 大部分情況下,系統(tǒng)缺省的參數(shù)都?jí)蚰闶褂?,任何一次參?shù)優(yōu)化調(diào)整都應(yīng)該有理論支撐和實(shí)踐證明,云調(diào)參張口就來(lái)誰(shuí)都會(huì)。
  • 并發(fā)調(diào)優(yōu)一定要抓住關(guān)鍵目標(biāo),找到核心資源,分析并發(fā)瓶頸鏈路。
  • 數(shù)據(jù)庫(kù)連接池還有很多參數(shù)是可以幫助調(diào)優(yōu)分析的,本文沒(méi)有介紹,jmeter的壓測(cè)的方式也是多種多樣,應(yīng)根據(jù)不同的場(chǎng)景調(diào)整。
  • 并發(fā)調(diào)優(yōu)是一個(gè)復(fù)雜的過(guò)程,從應(yīng)用所有的關(guān)聯(lián)點(diǎn)都可能出現(xiàn)問(wèn)題,本文只是從開(kāi)發(fā)角度,針對(duì)請(qǐng)求長(zhǎng)連接以及數(shù)據(jù)庫(kù)長(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)文章

最新評(píng)論

阿合奇县| 遵义县| 繁昌县| 潼南县| 丹阳市| 金阳县| 额济纳旗| 德庆县| 页游| 南澳县| 台东市| 辽宁省| 进贤县| 虞城县| 溆浦县| 清河县| 库伦旗| 镇江市| 将乐县| 中山市| 田东县| 宁强县| 河津市| 邢台县| 交口县| 沁水县| 元朗区| 阳泉市| 德清县| 上高县| 沅江市| 武城县| 绥滨县| 漳浦县| 丹阳市| 通州区| 乐都县| 枣阳市| 新泰市| 罗源县| 肇东市|