首頁 > 軟體

tomcat 叢集監控與彈性伸縮詳解

2022-09-12 18:02:51

如何給 tomcat 設定合適的執行緒池

任務分為 CPU 密集型和 IO 密集型

對於 CPU 密集型的應用來說,需要大量 CPU 計算速度很快,執行緒池如果過多,則儲存和切換上下文開銷過高反而會影響效能,可以適當將執行緒數量調小一些

對於 IO 密集型應用來說常見於普通的業務系統,比如會去查詢 mysql、redis 等然後在記憶體中做簡單的資料組裝和邏輯判斷,此時由於有 epoll、dma 等支援,消耗較長時間的執行緒不會長時間佔據 CPU 時間,所以可以適當將執行緒數量調大一些

對於執行緒數到底該設定多大,網上有很多的方案

我們在實際情況中基於 wrk 等壓測工具進行壓測,壓測邏輯相對複雜請求量相對較高的介面先設定一個相對保守的值

先預估假設當前介面處理平均耗時 300MS,1S 一個執行緒平均處理 3 個請求,最大 100 個執行緒 1S 能處理 300 TPS

粗略估算每個請求會消耗多大的內容空間,假如是 2KB,那麼每秒會消耗 600KB 以此來估算 YGC 多久會觸發一次,假設年輕代為 1GB 那麼大約 29 分鐘觸發一次 YGC

然後再看 100 個執行緒持續處理 300TPS 的時候 CPU 消耗情況

觀察記憶體幾分鐘 GC 一次,或者 CPU 使用率穩定在 50% 左右就好,假設此時執行緒池 70 個執行緒都在運作

那麼監控系統採集到執行緒池執行緒使用率達到了 80% 就開始擴容,因為擴容拉起新的伺服器到提供服務可能也需要1-2分鐘的時間,所以需要預留伺服器資源能夠處理彈性擴容期間的流量

實際場景中是相對比較複雜的,前期最好設定一個相對保守的擴容閾值,如果發現在達到這個閾值前伺服器已經頂不住了就可以實時的縮小閾值

同時還可以根據在達到擴容閾值擴容的時候,觀察線上真實的 CPU 和記憶體使用情況,可以基於 promethues + grafana 來進行監控,如果發現擴容時候 CPU 使用率和記憶體使用率 GC 評率比較低,那麼可以再設定中心動態的調整執行緒池的大小

所以說與其尋找一個準確的計算執行緒池數量的設定方式,不如提供一個可以動態調整的執行緒池作為 tomcat 的執行緒池

如何監控 tomcat 執行緒池的工作情況

spring boot 在啟動過程中會呼叫 TomcatProtocolHandlerCustomizer 的實現類,此處可以自定化 tomcat 執行緒池,也可以獲取到 tomcat 執行緒池

public class MyTomcatProtocolHandlerCustomizer implements TomcatProtocolHandlerCustomizer<ProtocolHandler> {
    private final ThreadPoolExecutor tomcatThreadPoolExecutor;
    public TomcatProtocolHandlerCustomizer(ThreadPoolExecutor tomcatThreadPoolExecutor) {
        this.tomcatThreadPoolExecutor = tomcatThreadPoolExecutor;
    }
    @Override
    public void customize(ProtocolHandler protocolHandler) {
        protocolHandler.setExecutor(tomcatThreadPoolExecutor);
    }
    public ThreadPoolExecutor getThreadPoolExecutor() {
        return tomcatThreadPoolExecutor;
    }
}

然後將執行緒池裝入一個容器 bean 中註冊到 promethues 監控中,當每秒進行採集的時候通過回撥的方法去獲取執行緒池的最新指標

promethues 每秒採集一次容器的執行緒池執行指標、伺服器測 CPU 使用率當滿足定義的擴容閾值時就拉起新的 POD

grafana 每秒採集一次 promethues 的資料匯聚圖示展示

@Bean
public AllServersThreadPoolCollector allServersThreadPoolCollector(@Qualifier(value = "GrpcThreadPoolExecutor") ThreadPoolExecutor GrpcThreadPoolExecutor,
                                                                   @Qualifier(value = "jdTomcatThreadPoolExecutor") org.apache.tomcat.util.threads.ThreadPoolExecutor TomcatThreadPoolExecutor,
                                                                   MeterRegistry registry) {
    return new AllServersThreadPoolCollector(GrpcThreadPoolExecutor, jdTomcatThreadPoolExecutor, registry);
}
public void init() {
    try {
        createGauge(this, "grpc_tomcat_core_pool_size", pool -&gt; this.getCorePoolSize());
        createGauge(this, "grpc_tomcat_maximum_pool_size", pool -&gt; this.getMaximumPoolSize());
        createGauge(this, "grpc_tomcat_busy_pool_size", pool -&gt; this.getActiveCount());
        createGauge(this, "grpc_tomcat_wait_queue_size", pool -&gt; this.getWaitQueueSize());
    } catch (Exception e) {
        log.error("註冊 all servers 監控失敗", e);
    }
}
private void createGauge(AllServersThreadPoolCollector weakRef, String metric, ToDoubleFunction&lt;AllServersThreadPoolCollector&gt; measure) {
    Gauge.builder(metric, weakRef, measure)
            .register(this.registry);
}
public int getWaitQueueSize() {
    return grpcThreadPoolExecutor.getQueue().size() + tomcatThreadPoolExecutor.getQueue().size();
}

tomcat 執行緒池擴縮容

Java 執行緒池是支援直接修改 corePoolSize、maximumPoolSize 的

  • 修改 corePoolSize

(1)數量小於 maximumPoolSize 拋異常

(2)如果 corePoolSize - 原來的 = delta,delta 大於 0 那麼建立等待佇列任務數量和 delta 個執行緒來處理等待處理的任務

(3)如果正在執行的執行緒數量 > corePoolSize 那麼就中斷多餘的執行緒

  • 修改 maximumPoolSize

(1)maximumPoolSize 小於 corePoolSize 拋錯

(2)如果執行的執行緒大於 maximumPoolSize 中斷掉一些空閒的執行緒

基於這些機制就能在執行期間動態調整執行緒池內容

無需擔心會中斷掉正在執行的任務,因為執行緒池 worker 執行緒每執行一個任務的時候

tomcat 是如何避免原生執行緒池的缺陷的

原生執行緒池的工作原理

(1)執行的執行緒數小於核心執行緒,就建立一個 worker 執行緒去執行任務

(2)執行的執行緒數 >= 核心執行緒了,將任務全部積壓到佇列中

(3)佇列如果滿了繼續建立非核心執行緒 worker 去執行任務

假如 tomcat 採用了原生執行緒池,核心執行緒為 10 個,最大執行緒為 100,佇列為 200,並行來了 100 個請求,那麼同時系統只能處理 10 個,剩下 90 個都得放入佇列中讓 10 個核心執行緒慢慢排隊處理,延時必然非常高

tomcat 如何優化執行緒池,核心在於阻塞佇列的實現,因為阻塞佇列滿了才會繼續建立非核心 worker 執行緒處理任務

(1)執行的執行緒數小於核心執行緒,就建立一個 worker 執行緒去執行任務

(2)當前已經建立的核心+非核心執行緒數等於最大執行緒數,任務壓入佇列

(3)正在處理的請求數量小於核心+非核心執行緒數,任務壓入佇列

(4)當前已經建立的核心+非核心執行緒數小於最大執行緒數,建立 worker 執行緒處理請求

總結就是一句話當高並行流量過來的時候,會去建立最大執行緒數的 worker 去處理請求用以降低尾延遲,超過最大執行緒後,任務將被壓入佇列中進行處理

以上就是tomcat 叢集監控與彈性伸縮詳解的詳細內容,更多關於tomcat 叢集監控彈性伸縮的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com