Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available

org.apache.tomcat.jdbc.pool.ConnectionPool

查看源碼

private PooledConnection borrowConnection(int wait, String username, String password) throws SQLException {
 
        if (isClosed()) {
            throw new SQLException("Connection pool closed.");
        } //end if
 
        //get the current time stamp
        long now = System.currentTimeMillis();
        //see if there is one available immediately
        PooledConnection con = idle.poll();
 
        while (true) {
            if (con!=null) {
                //configure the connection and return it
                PooledConnection result = borrowConnection(now, con, username, password);
                borrowedCount.incrementAndGet();
                if (result!=null) return result;
            }
 
            //if we get here, see if we need to create one
            //this is not 100% accurate since it doesn't use a shared
            //atomic variable - a connection can become idle while we are creating
            //a new connection
            if (size.get() < getPoolProperties().getMaxActive()) {
                //atomic duplicate check
                if (size.addAndGet(1) > getPoolProperties().getMaxActive()) {
                    //if we got here, two threads passed through the first if
                    size.decrementAndGet();
                } else {
                    //create a connection, we're below the limit
                    return createConnection(now, con, username, password);
                }
            } //end if
 
            //calculate wait time for this iteration
            long maxWait = wait;
            //if the passed in wait time is -1, means we should use the pool property value
            if (wait==-1) {
                maxWait = (getPoolProperties().getMaxWait()<=0)?Long.MAX_VALUE:getPoolProperties().getMaxWait();
            }
 
            long timetowait = Math.max(0, maxWait - (System.currentTimeMillis() - now));
            waitcount.incrementAndGet();
            try {
                //retrieve an existing connection
                con = idle.poll(timetowait, TimeUnit.MILLISECONDS);
            } catch (InterruptedException ex) {
                if (getPoolProperties().getPropagateInterruptState()) {
                    Thread.currentThread().interrupt();
                }
                SQLException sx = new SQLException("Pool wait interrupted.");
                sx.initCause(ex);
                throw sx;
            } finally {
                waitcount.decrementAndGet();
            }
            if (maxWait==0 && con == null) { //no wait, return one if we have one
                if (jmxPool!=null) {
                    jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - no wait.");
                }
                throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                        "NoWait: Pool empty. Unable to fetch a connection, none available["+busy.size()+" in use].");
            }
            //we didn't get a connection, lets see if we timed out
            if (con == null) {
                if ((System.currentTimeMillis() - now) >= maxWait) {
                    if (jmxPool!=null) {
                        jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.POOL_EMPTY, "Pool empty - timeout.");
                    }
                    throw new PoolExhaustedException("[" + Thread.currentThread().getName()+"] " +
                        "Timeout: Pool empty. Unable to fetch a connection in " + (maxWait / 1000) +
                        " seconds, none available[size:"+size.get() +"; busy:"+busy.size()+"; idle:"+idle.size()+"; lastwait:"+timetowait+"].");
                } else {
                    //no timeout, lets try again
                    continue;
                }
            }
        } //while
    }

參數設置

屬性名描述默認值
driverClassName用戶名-
url密碼-
username建立連接的url-
password數據庫驅動的完整類名-
initialSize連接器啓動時創建的初始連接數10
maxActive最大連接數,通常爲常規訪問的最大數據庫併發數,建議根據後期jmx監控逐漸調優100
maxIdle最大空閒連接數,比較難把握的一個參數,許多連接池也已經移除了此屬性(如Druid),訪問峯值比較集中的系統如考勤可以設置小一點節省大部分時段的連接資源,過小也可能導致連接頻繁創建關閉也會影響性能,建議一般系統不低於maxActive的50%100
minIdle最小連接數,一般與initialSize一致即可10
maxWait連接池中連接用完時,新的請求的等待時間,超時返回異常,單位毫秒默認30000
testWhileIdle連接進入空閒狀態時是否經過空閒對象驅逐進程同時進行校驗,推薦的校驗方法,依賴validationQueryfalse
validationQuery在連接返回給調用者前用於校驗連接是否有效的SQL語句,必須爲一個SELECT語句,且至少有一行結果-
validationQueryTimeout連接驗證的超時時間,單位秒,注:池本身並不會讓查詢超時,完全是依靠JDBC驅動來強制查詢超時-
validationIntervalTomcatJDBC特有屬性,檢查連接可用性的時間間隔,防止testOnBorrow和testOnReturn爲true時檢查過於頻繁,單位毫秒30000
timeBetweenEvictionRunsMillis空閒對象驅逐檢查時間間隔,單位毫秒5000
minEvictableIdleTimeMillis連接被空閒對象驅逐進程驅逐前在池中保持空閒狀態的最小時間,單位毫秒60000
defaultAutoCommit連接池所創建的連接默認自動提交狀態(JDBC缺省值意思是默認不會調用setAutoCommit方法)JDBC缺省值
jmxEnabled是否利用 JMX 註冊連接池true
jdbcInterceptorsTomcatJDBC特有屬性, QueryTimeoutInterceptor(查詢超時攔截器,屬性queryTimeout,單位秒,默認1秒),SlowQueryReport(慢查詢記錄,屬性threshold超時紀錄閾值單位毫秒,默認1000),多個用攔截器用;分隔,示例:QueryTimeoutInterceptor(queryTimeout=5);SlowQueryReport(threshold=3000)注:當新語句創建時,自動調用Statement.setQueryTimeout(seconds)。池本身並不會讓查詢超時,完全是依靠JDBC驅動來強制查詢超時,更詳細的信息請查看官方文檔-
testOnBorrow連接被調用時是否校驗,依賴validationQuery,對性能有一定影響,不推薦使用false
testOnReturn連接返回到池中是時是否校驗,依賴validationQuery,對性能有一定影響,不推薦使用false
removeAbandoned是否清除已經超過 removeAbandonedTimeout 設置的連接,可用於排查一些事務未提交的問題(正式環境謹慎使用,對性能有一定影響),不推薦使用,可用QueryTimeOut攔截器替代false
removeAbandonedTimeout清除無效連接的時間,單位秒 與removeAbandoned聯合使用60
defaultReadOnly連接池創建的連接是否是否爲只讀,需要說明的是設置了true只是告訴數據庫連接是隻讀,便於數據庫做一些優化(例如不安排數據庫鎖),並非不能執行更新操作,只是對數據的一致性的保護並不強而已(這跟spring的只讀事務類似)JDBC缺省

解決方法(參考)

基本上來說,大部分項目都需要跟數據庫做交互,那麼,數據庫連接池的大小設置成多大合適呢?

連接數計算公式

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

核心數不應包含超線程(hyper thread),即使打開了超線程也是如此,如果熱點數據全被緩存了,那麼有效磁盤數實際是0,隨着緩存命中率的下降,有效磁盤數也逐漸趨近於實際的磁盤數。另外需要注意,這一公式作用於SSD 的效果如何,尚未明瞭。

好了,按照這個公式,如果說你的服務器 CPU 是 4核 i7 的,連接池大小應該爲 ((4*2)+1)=9。

取個整, 我們就設置爲 10 吧。你這個行不行啊?10 也太小了吧!

你要是覺得不太行的話,可以跑個性能測試看看,我們可以保證,它能輕鬆支撐 3000 用戶以 6000 TPS 的速率併發執行簡單查詢的場景。你還可以將連接池大小超過 10,那時,你會看到響應時長開始增加,TPS 開始下降。

比如說,你的系統同時混合了長事務和短事務,這時,根據上面的公式來計算就很難辦了。正確的做法應該是創建兩個連接池,一個服務於長事務,一個服務於"實時"查詢,也就是短事務。

還有一種情況,比方說一個系統執行一個任務隊列,業務上要求同一時間內只允許執行一定數量的任務,這時,我們就應該讓併發任務數去適配連接池連接數,而不是連接數大小去適配併發任務數。

在配置tomcat數據庫連接池時候,對配置的具體數值總是懵逼。這裏給出具體建議。

首先上公式:
數據庫連接池連接數 = ((核心數 * 2) + 有效磁盤數)
核心數如何得到?

linux 查看物理cpu的個數

 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

查看每個物理CPU中core的個數(即核數)

 cat /proc/cpuinfo| grep "cpu cores"| uniq

兩數相乘即得到核心數。

有效磁盤數一般是一個。這樣得到的數據庫連接池個數是不是和自己猜想的小的很多?
下面進行理解一下。

單核 CPU同一時刻只能執行一個線程,然後操作系統切換上下文,CPU 核心快速調度,執行另一個線程的代碼,不停反覆,給我們造成了所有進程同時運行假象,其實這時候上下文切換的性能損耗很大。

其實,在一核 CPU 的機器上,順序執行A和B永遠比通過時間分片切換“同時”執行A和B要快,因爲一旦線程的數量超過了 CPU 核心的數量,再增加線程數系統就只會更慢,而不是更快,因爲這裏上下文切換會耗費額外的性能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章