druid下莫名其妙hold15分鐘+。疑是socket timeout超時15分鐘後,重建了新鏈接致使

背景

在應用端經過mybatis的interceptor自定義Plugin攔截Executor, 統計輸出sql的執行耗時。html

今天生產發生一個很奇怪的問題: 莫名其妙卡頓15分鐘+,其後正常返回sql正常結果! 使用druid版本是1.0.2。。。。。java

日誌分析

 統計發現:mysql

  1. 出現該狀況的單量有6筆,集中在特定的2個小時以內,都是查詢sql;都發生在1臺應用服務器上。
  2. 在這幾筆訂單卡住的時間內,輪詢任務觸發又正常查詢成功並正確處理成功!
  3. 數據庫層面沒有慢sql;且數據庫實例的指標監控穩定,應用監控除sql耗時監控異常外,其餘一切正常。
  4. 日誌沒有輸出error

推測是不是在getConnection過程當中出現了等待。但因獲取鏈接時的日誌過少找不到具體的緣由!git

DruidDataSource的testConnectionInternal

網上的相似問題說的緣由是Druid在從鏈接池中獲取的鏈接(代碼詳見:DruidDataSource的getConnectionDirect),在開啓testWhileIdle時,若是活躍時間距離當前比較久,那麼將驗證該鏈接是否有效,無效會discardConnection並再次去pool內獲取github

這個過程可能被數據庫層面的防火牆策略已經關閉該鏈接,而客戶端還傻傻的發sql驗證直到超時。。。。spring

在該測試鏈接的方法中有一個查詢超時超時時間:validationQueryTimeout。測試中是-1, 執行的是默認時間。。。。這個默認時間沒有找到!! (有找到MappedStatementConfig由SqlMapConfiguration的defaultStatementTimeout)sql

深刻理解JDBC的超時設置

應用與數據庫間的timeout層級實例:
轉帖: 深刻理解JDBC的超時設置
數據庫

 上層的timeout依賴於下層的timeout,只有下層的timeout無誤時,上層的timeout才能確保正常。eg.  當socket timeout出現問題時,上層的statement timeout和transaction timeout都將失效。
 statement timeout 沒法處理網絡鏈接失敗時的超時,它能作的僅僅是限制statement的操做時間;網絡鏈接失敗時的timeout必須交由JDBC來處理;JDBC的socket timeout會受到操做系統socket timeout設置的影響。api

  1. Transaction Timeout: 通常存在於框架(Spring, EJB)或應用級。spring在配置事務切面的時候能夠配置timeout。服務器

  2. Statement Timeout: 經過調用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API進行設置。 以myBatis爲例,statement timeout的默認值能夠經過sql-map-config.xml中的defaultStatementTimeout 屬性進行設置。同時,也能夠設置sqlmap中select,insert,update標籤的timeout屬性,從而對不一樣sql語句的超時時間進行獨立的配置。

  3. JDBC的statement timeout:  經過調用Connection的createStatement()方法建立statement來executeQuery時,會註冊一個 TimerTask ->CancelTask 用來給Timer -> ConnectionImpl.getCancelTimer()來處理timeout事項:發送 "KILL QUERY」 。 代碼詳見: StatementImpl  ConnectionImpl

  4. JDBC的socket timeout: 

    1. 因爲TCP/IP的結構緣由,socket沒有辦法探測到網絡錯誤,所以應用也沒法主動發現數據庫鏈接斷開。若是沒有設置socket timeout的話,應用在數據庫返回結果前會無期限地等下去,這種鏈接被稱爲dead connection。 爲了不dead connections,socket必需要有超時配置。socket timeout能夠經過JDBC設置,socket timeout可以避免應用在發生網絡錯誤時產生無休止等待的狀況,縮短服務失效的時間。不推薦使用socket timeout來限制statement的執行時長,所以socket timeout的值必需要高於statement timeout,不然,socket timeout將會先生效,這樣statement timeout就變得毫無心義,也沒法生效。
      eg.   jdbc:mysql://xxx.xx.xxx.xxx:3306/database?connectTimeout=60000&socketTimeout=60000

  5. 操做系統的socket timeout配置


     

番外

MySQL JDBC的queryTimeout的一個坑

在該文章中說Statement.setQueryTimeout : 若是設置的超時時間過長,在當大批量的SQL同時執行時,每個SQL都會建立一個CancelTask對象,雖然很快執行完,且會調用CancelTask.cancel()方法,可是CancelTask方法的源代碼僅僅是將本身的狀態修改成:CANCELLED,而並不會直接從隊列中移除這個對象, 致使OOM。 建議在cancel()後須要purge一下。

解: MySQL Connector Java 5.1.44  版本 的 StatementImpl 已經有了purge; 且 在1.8版本的JDK對Timer的cancel()也增長了移除queue內容

Timer

public int purge() {
         int result = 0;

         synchronized(queue) {
             for (int i = queue.size(); i > 0; i--) {
                 if (queue.get(i).state == TimerTask.CANCELLED) {
                     queue.quickRemove(i);
                     result++;
                 }
             }

             if (result != 0)
                 queue.heapify();
         }

         return result;
     }
}

    public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

相似問題

經過網上blog查找發現有類似的問題存在:

  1. https://www.oschina.net/question/1450045_2157629

  2. https://www.2cto.com/database/201505/402016.html

  3. 在issues中搜索druid的testWhileIdle問題: https://github.com/alibaba/druid/issues/1260

相關文章
相關標籤/搜索