數據庫連接池調優


錯誤看法:

  之前我認為數據庫連接池盡量設置的大些,越大數據庫的性能越高,吞吐量越高。

  現在來看是錯誤的。

 

性能測試:

 先看一個連接池越大反而性能越低的例子(前提:單機數據庫一般承受的QPS在1000):

 Oracle性能小組發布的連接池大小性能測試,假設並發量為1萬。模擬了9600個並發線程來操作數據庫,每兩次數據庫操作之間sleep 550ms,測試用例及結果為:

 1):連接池數為2048,結果每個請求要在連接池隊列中等待33ms,獲得連接之后,執行SQL耗時77ms,CPU消耗在95%左右。

 2):連接池數為1024,結果每個請求要在連接池隊列中等待38ms,獲得連接之后,執行SQL耗時30ms,耗時減少很多。

   兩次比較結果為吞吐量基本沒變,但是連接池數減半之后wait事件也減少了一半。

 3):連接池數為96,結果每個請求在連接池隊列中平均等待時間為1ms,SQL執行耗時為2ms。吞吐量大大提高。

  

  這是因為一核的CPU同一時刻只能執行一個線程,多個線程並發執行的話操作系統為每個線程分配時間片,然后快速切換時間片,執行其他線程,不停反復,給我們造成所有線程同時運行的假象。

  因此單核CPU順序執行AB兩個線程永遠比並發切換時間片執行AB要快!

  一旦線程的數量超過了 CPU 核心的數量,再增加線程數系統就只會更慢,而不是更快,因為這里涉及到上下文切換耗費的額外的性能。

  

其他影響性能的因素

  1)CPU

  2)磁盤IO

  3)網絡IO

  CPU:

  暫不考慮磁盤IO和網絡IO,只看CPU的話,在一個8核的服務器上,數據庫連接數&線程數設置為8(與核心相同)能夠提供最優的性能,如果再增加連接數,反而會因為上下文切換導致性能下降。

  磁盤IO:

  數據庫通常把數據存儲在磁盤上,磁盤讀寫尋址的時候,線程需要阻塞等待着磁盤(IO等待),此時操作系統可以將那個空閑的CPU核心用於服務其他線程。所以,由於線程總是在IO上阻塞,我們可以讓線程/連接數比CPU核心多一些,這樣能夠在同樣的時間內完成更多的工作。

  較新型的SSD不需要尋址,也沒有旋轉的碟片。但是別認為應該增加更多的線程數,因為無需尋址和沒有旋回耗時意味着更少的阻塞(CPU不會因阻塞而空閑),所以更少的線程【更接近於CPU核心數】會發揮出更高的性能。只有當阻塞創造了更多的執行機會時(CPU因阻塞而空閑),更多的線程數才能發揮出更好的性能。

  網絡IO:

  網絡和磁盤類似,通常以太網接口讀寫數據時也會形成阻塞,10G帶寬會比1G帶寬的阻塞少一些,1G帶寬又會比100M帶寬的阻塞少一些。不過網絡通常是放在第三位考慮的,有些人會在性能計算中忽略它們。

 

總結

  尋找最合適的連接數可以參考下面這個公式:

    連接數 =(核心數*2)+ 有效磁盤數

  一般服務器的磁盤個數都是1,因此CPU為4核的數據庫服務器的連接池大小應該為(4*2)+1=9,取整為10。

  根據性能壓力測試,這個值能輕松搞定3000用戶以6000TPS的速率並發執行查詢的場景,如果連接數超過10,就會看到響應時長開始增加,TPS開始下降。

  即連接池中的連接數量應該等於你的數據庫能夠有效同時進行的查詢任務數(通常不會高於2*CPU核心數)

  注:這一公式其實不僅適用於數據庫連接池的計算,大部分涉及計算和I/O的程序,線程數的設置都可以參考這一公式。

 

    我們需要的是一個小連接池和一個大的飽和等待連接的線程的隊列

  如果並發數為10000,我們需要一個大小為10的連接池,然后讓剩下的業務線程在隊列里等待就可以了。

 

  附:數據源配置

    @Bean
    public BasicDataSource globalDataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl(jdbc:mysql://xxx:3306/xxx);
        dataSource.setUsername(xxx);
        dataSource.setPassword(xxx);
        // 分配的最大連接數(推薦2N,N為數據庫服務器cpu核心數),負值表示無限制
        dataSource.setMaxActive(10);
        dataSource.setMaxIdle(2);
        dataSource.setMinIdle(1);
        dataSource.setMaxWait(6000);
        // DBA推薦連接池配置
        // 用來驗證連接是否生效的sql語句
        dataSource.setValidationQuery("SELECT 1");
        // 從池中獲取連接前進行驗證
        dataSource.setTestOnBorrow(true);
        // 向池中還回連接前進行驗證
        dataSource.setTestOnReturn(false);
        // 連接空閑時驗證
        dataSource.setTestWhileIdle(true);
        // 運行判斷連接超時任務(evictor)的時間間隔,單位為毫秒,默認為-1,即不執行任務
        dataSource.setTimeBetweenEvictionRunsMillis(60000);
        // 連接的超時時間,默認為半小時
        dataSource.setMinEvictableIdleTimeMillis(60000);
        return dataSource;    
}

 

  參考:https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM