Thymeleaf+SpringBoot2高吞吐量調優技巧

banq發表於2024-03-25

Thymeleaf+SpringBoot2技術如下:

  • Springboot 2.3 + Thymeleaf
  • MongoDB
  • Java

提前監控一些指標:
SpringBoot服務:

  • API 響應程式碼(5xx 和 4xx)
  • 每個端點的延遲
  • 每個端點每秒的請求數
  • tomcat 指標(Servlet 錯誤、連線、當前執行緒)
  • 中央處理器
  • 記憶

資料庫叢集:
  • 熱門查詢
  • 複製延遲
  • 讀/寫延遲
  • 慢速查詢
  • 檔案鎖
  • 系統鎖
  • 中央處理器
  • 當前會話

對於 SpringBoot,這可以透過啟用管理端點並使用 Prometheus 收集指標並將指標傳送到 Grafana 或 Cloudwatch 等方式輕鬆衡量。指標交付後,在合理的閾值上設定警報。

對於資料庫,這取決於技術,您應該在客戶端(spring boot dbmetrics)和伺服器端都對其進行監控。客戶端的監控對於檢視是否有任何代理或防火牆不時阻止您的任何命令非常重要。相信我,即使您測試了它並且看起來工作得很好,這些連線丟失也可能會發生,以防萬一某些東西配置不正確並且您想要捕獲它!例如:代理 sidecar 上的資料庫埠上的出站流量配置錯誤可能會導致 Spring Boot 端出現髒 HTTP 連線,而這些連線已在伺服器端關閉。

1、連線池和 HTTP 客戶端調優
我檢查的第一件事是連線池和 HTTP 客戶端設定,這樣我就可以瞭解這些服務可以開啟的最大並行連線數、在所有當前連線都繁忙後開始拒絕新連線的速度以及持續多長時間。會等待響應,直到開始超時。

超時
比喻:一條很長的超市隊伍,收銀員很慢,隊伍無限地增長,而你卻站在隊伍的最後。你要麼永遠等待,也許在商店關門之前就排到隊伍的前面(其他人會一直在你後面排隊),或者你(和所有其他人)可以在 3 秒後決定離開那個擁擠的地方,然後過來晚點回來。

超時是一種為客戶端提供快速響應的機制,避免上游服務等待並阻止它們接受新請求。另一方面,斷路器是一種保護措施,可避免在出現問題(連線斷開、CPU 過載等)時使下游服務過載。例如,斷路器是指當餐廳滿員時,那些服務員將顧客送回家而沒有機會等待餐桌的情況。

連線池
遠端連線(例如用於與資料庫或 REST API 通訊的連線)是為每個請求建立的昂貴資源。它需要開啟連線、建立握手、驗證證書等。

連線池允許我們重用連線來最佳化效能,並透過維護多個並行連線(每個連線都在其自己的執行緒中)來增加應用程式的併發性。在給定某些配置的情況下,如果池中的所有連線都繁忙,它還使我們能夠靈活地將請求排隊一定時間,這樣它們就不會立即被拒絕,從而使我們的服務有更多機會在一定時間內成功服務所有請求。

@Bean
HttpClient httpClient(PoolingHttpClientConnectionManager connectionManager) {
    return HttpClientBuilder.create()
            .setConnectionManager( connectionManager )
            .build();
}

@Bean PoolingHttpClientConnectionManager connectionManager() {
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal( POOL_MAX_TOTAL );
    connectionManager.setDefaultMaxPerRoute( POOL_DEFAULT_MAX_PER_ROUTE );
    return connectionManager;
}

@Bean
ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    factory.setConnectTimeout(CONNECT_TIMEOUT_IN_MILLISECONDS);
    factory.setReadTimeout(READ_TIMEOUT_IN_MILLISECONDS);
    factory.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT);
    return factory;
}

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    RestTemplate restTemplate = new RestTemplateBuilder()
        .requestFactory(() -> clientHttpRequestFactory)
        .build();
    return restTemplate;
}

上面的 beans 將確保其餘模板使用的 HTTP 客戶端使用一個連線管理器,該管理器具有合理的每條路由的最大連線數和總最大連線數。如果傳入請求多於我們能夠透過這些設定提供服務的數量,它們將由連線管理器排隊,直到達到連線請求超時。如果連線請求超時後由於請求仍在佇列中而未執行連線嘗試,則請求將失敗。請在此處閱讀有關不同型別的 HTTP 客戶端超時的更多資訊。

確保根據您的需求和伺服器資源調整常量。請注意,每個連線都會開啟一個執行緒,並且執行緒受到作業系統資源的限制。因此,您不能簡單地將這些限制增加到不合理的值。

2、MongoDB調優
為 MongoDB 叢集設定了監控,這樣就很容易發現罪魁禍首:有一個文件因對同一文件進行多次併發寫入嘗試而被鎖定。

以上更改確實增加了吞吐量,現在我們的資料庫因同一文件中的大量並行寫入而過載,這導致需要花費大量時間等待解鎖以便下一個查詢更新它。

資料庫連線池忙於對請求進行排隊,因此上游服務也開始讓其執行緒池用於處理傳入請求,因為等待響應的其餘模板的同步性質,增加了上游服務的 CPU 消耗。

文件鎖對於併發來說是必要的,但效果並不好,因為它們可以很容易地開始阻止您的資料庫連線,並且它們通常表明您的程式碼或集合設計存在問題,因此請務必檢查它,以防您看到一些跡象表明您的文件正在被破壞。

刪除不必要的 save() 呼叫後,事情開始看起來好多了。

下一步:MongoDB 允許您透過其連線選項覆蓋預設值。

  • 連線將根據 minPoolSize 和 maxPoolSize 建立。如果執行查詢需要更長的時間並且有新查詢進入,則將建立新連線,直到達到 maxPoolSize。
  • 我們還可以使用 waitQueueTimeoutMS 定義查詢可以等待執行的時間。
  • 資料庫寫入時:,您還應該確保檢視 wtimeoutMS,預設情況下它會保持連線繁忙,直到資料庫完成寫入。
  • 如果設定的值不同於預設值(永不超時),您還可以在資料庫周圍設定一個斷路器,以確保不會因為額外的請求而使其過載。
  • 如果您的資料庫叢集包含多個節點,請確保透過設定 readPreference=secondaryPrefered 來分配讀取負載,並注意一致性、讀取隔離和新近度

3、Thymeleaf快取
Thymeleaf為基於內容的靜態資源啟用了快取。類似於以下屬性:

spring.resources.chain.enabled=true
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=<font>/**

然而,這三行引入了兩個問題。
1- 根據資源內容啟用快取。但是,對於每個請求,都會一遍又一遍地從磁碟讀取內容,因此可以重新計算其雜湊值,因為快取本身的雜湊計算結果不會被快取。要解決此問題,請不要忘記新增以下屬性:
spring.resources.chain.cache=true

2-不幸的是,該服務沒有使用任何基本路徑來統一靜態資源的解析度,因此基本上,Thymeleaf 預設情況下會嘗試將每個連結作為靜態資源從磁碟載入,即使它們只是控制器路徑,例如。請記住,磁碟操作通常非常昂貴。

因為我不想透過將所有靜態資源移動到資原始檔夾中的新目錄來引入不相容的更改,因為這會導致連結更改,並且我有非常明確定義的靜態資源路徑,所以我可以簡單地解決它:使用ResourceHandlerRegistration 中的setOptimizeLocations()

相關文章