簡體   English   中英

Java中的連接池和線程池設置

[英]Connection Pool and thread pool setting in Java

Spring 應用程序使用 Hikari 池。

現在對於來自客戶端的單個請求,我必須查詢 10 個表(業務需要),然后將結果組合在一起。 查詢每個表可能需要 50 毫秒到 200 毫秒。 為了加快響應時間,我在我的服務中創建了一個FixedThreadPool來查詢不同線程中的每個表(偽代碼):

class MyService{
    final int THREAD_POOL_SIZE = 20;
    final int CONNECTION_POOL_SIZE = 10;


    final ExecutorService pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    protected DataSource ds;


    MyClass(){
        Class.forName(getJdbcDriverName());
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(CONNECTION_POOL_SIZE);
        ds = new HikariDataSource(config);
    }



    public Items doQuery(){
        String[] tables=["a","b"......]; //10+ tables
        Items result=new Items();
        CompletionService<Items> executorService = new ExecutorCompletionService<Items>(pool);
        for (String tb : tables) {
            Callable<Item> c = () -> {
                Items items = ds.getConnection().query(tb); ......
                return Items;
            };
            executorService.submit(c);
        }


        for (String tb: tables) {
            final Future<Items> future = executorService.take();
            Items items = future.get();
            result.addAll(items);
        }
    }
}

現在對於單個請求,平均響應時間可能為 500 毫秒。

在此處輸入圖像描述

但是對於並發請求,平均響應時間會迅速增加,請求越多,響應時間就會越長。

在此處輸入圖像描述

我想知道如何設置正確的連接池大小和線程池大小以使應用程序有效工作?

順便說一句,該數據庫在雲中使用 RDS,具有 4 個 cpu 16GB 內存、2000 個最大連接數和 8000 個最大 IOPS。

您可能需要考慮更多參數:
1. 數據庫的最大並發請求參數。 雲提供商對不同層的並發請求有不同的限制,您可能需要檢查一下。

2. 說50-200ms,雖然很難說,是平均有8個50ms的請求和2個200ms的請求,還是幾乎都差不多? 為什么? 您的 doQuery 可能會受到最長查詢時間(即 200 毫秒)的限制,但需要 50 毫秒的線程將在其任務完成后釋放,使其可用於下一組請求。

3. 您期望收到的 QPS 是多少?

一些計算:如果單個請求占用 10 個線程,並且您配置了 100 個連接和 100 個並發查詢限制,假設每個查詢 200ms,您一次只能處理 10 個請求。 如果大多數查詢需要 50 毫秒左右,可能會比 10 好一點(但我不會樂觀)。

當然,如果您的任何查詢花費超過 200 毫秒(網絡延遲或其他任何東西),其中一些計算就會被折騰,在這種情況下,我建議您在連接端有一個斷路器(如果您被允許中止超時后的查詢)或在 API 結束。

注意最大連接限制最大並發查詢限制不同。

建議:由於需要500ms以下的響應,池上也可以有100-150ms左右的connectionTimeout。 最壞的情況:150 毫秒連接超時 + 200 毫秒查詢執行 + 100 毫秒應用程序處理 < 500 毫秒的響應。 作品。

您可以創建自定義線程執行器

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

    private CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                                     long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    /**
     * Returns a fixed thread pool where task threads take Diagnostic Context from the submitting thread.
     */

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new CustomThreadPoolExecutor(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }
}

在配置中,您可以配置 ExecutorService bean,如下所示

@Bean
    public ExecutorService executeService() {
        return CustomThreadPoolExecutor.newFixedThreadPool(10);
    }

這是創建自定義線程池執行器的最佳實踐

調整連接池大小的正確方法通常是將其保留為默認值。

來自 hikari 網站

如果您有 10,000 個前端用戶,那么擁有 10,000 個連接池將是瘋狂的。 1000仍然可怕。 甚至100個連接,矯枉過正。 您想要一個最多包含幾十個連接的小池,並且您希望應用程序線程的 rest 在等待連接的池中阻塞。 如果對池進行了適當的調整,它將被設置為數據庫能夠同時處理的查詢數量的限制——這很少超過(CPU 核心 * 2),如上所述。

假設您知道每個請求將消耗 10 個線程,那么您想要打破這個建議和 go 以獲得更多線程 - 將其保持在小於 100 的數字可能會提供足夠的容量。

我會像這樣實現 controller:

使用CompletableFuture使您的查詢在 controller / 服務類中異步,並讓連接池擔心保持其線程忙碌。

所以 controller 可能看起來像這樣(我從其他一些不像這個例子那樣工作的代碼中改編了這個,所以這個代碼是鹽粒):

public class AppController { 

    @Autowired private DatabaseService databaseService; 

    public ResponseEntity<Thing> getThing() { 
        CompletableFuture<Foo> foo = CompletableFuture.runAsync(databaseService.getFoo());
        CompletableFuture<Bar> bar = CompletableFuture.runAsync(databaseService.getBar());
        CompletableFuture<Baz> baz = CompletableFuture.runAsync(databaseService.getBaz());

        // muck around with the completable future to return your data in the right way
        // this will be in there somewhere, followed by a .thenApply and .join
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(foo, bar, baz);

        return new ResponseEntity<Thing>(mashUpDbData(cf.get()));
    }    
}

controller 將產生您允許ForkJoinPool使用的盡可能多的線程,它們將同時敲擊數據庫,並且連接池可能會擔心保持連接處於活動狀態。

但我認為您在小負載下看到響應時間井噴的原因是,JDBC 在等待數據從數據庫返回時阻塞了線程。

要阻止如此劇烈地影響響應時間的阻塞,您可以嘗試spring 引導響應式樣式。 這使用異步 io 和背壓來匹配 IO 生產到消費,基本上這意味着應用程序線程盡可能繁忙。 應該會在響應時間以線性方式增加的負載下停止該行為。

請注意,如果您執行 go 反應路徑,則 jdbc 驅動程序仍然阻塞,因此 spring 有很大的推動力來創建反應數據庫驅動程序

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM