您好,登錄后才能下訂單哦!
今天小編給大家分享一下springboot+atomikos+druid數據庫連接失效的原因是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
最近查看系統的后臺日志,經常發現這樣的報錯信息:The last package successfully received from the server was 40802382 milliseconds ago,截圖如下所示。
由于我們的系統都是在白天使用,夜里基本上沒有用戶使用,再加上以上的報錯信息都是出現在早晨,結合錯誤日志初步分析,應該是數據庫連接超時自動斷開了。百度一番后,得知Mysql的默認連接時間是8小時,超過8小時沒有操作后就會自動斷開連接,但是已經使用了druid數據庫連接池,按理說已經對數據庫連接做了保護和檢查,不應該出現這樣的問題。要想徹底弄明白這個問題,就只能去研究druid數據庫連接池框架了。
項目的數據庫連接池基本配置信息如下所示
通過以上的配置分析得知,一個數據庫連接從連接池中借出后經過21600s即6小時后會被強制回收,不會超過Mysql的默認8小時,而且也不存在這么長時間的事務,所以不太可能是因為數據庫連接借出超時導致上面的錯誤,那么就是從數據庫連接池中申請的連接已經超時了?似乎也不太可能,因為有檢查機制,即每隔30s就會檢查一次連接池中的連接是否超時,并且連接池中允許存在的空閑連接最大時間為540s。這就奇怪了,到底是什么原因導致上面的錯誤呢?這時注意到上述錯誤堆棧中的com.atomikos.datasource.pool.ConnectionPool.findOrWaitForAnAvailableConnection。是否問題的原因在于使用了Atomikos呢,帶著這樣的疑惑去閱讀了Druid和Atomikos相關的源碼。
由于Atomikos連接池是基于Druid連接池之上的,所以Atomikos新建和銷毀數據庫連接都是從Druid連接池中借出和歸還數據庫連接,而不是直接與數據庫交互,那么我們就來看看Druid是如何維持數據庫連接的。
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException { //初始化檢查配置和后臺線程 init(); if (filters.size() > 0) { FilterChainImpl filterChain = new FilterChainImpl(this); return filterChain.dataSource_connect(this, maxWaitMillis); } else { return getConnectionDirect(maxWaitMillis); } }
從Druid連接池中獲取數據庫連接,先調用init()方法進行初始化工作,然后調用getConnectionDirect()獲取連接。
decrementPoolingCount(); DruidConnectionHolder last = connections[poolingCount]; connections[poolingCount] = null;
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); public DruidPooledConnection(DruidConnectionHolder holder){ super(holder.getConnection()); this.conn = holder.getConnection(); this.holder = holder; this.lock = holder.lock; dupCloseLogEnable = holder.getDataSource().isDupCloseLogEnable(); ownerThread = Thread.currentThread(); connectedTimeMillis = System.currentTimeMillis(); }
上述是獲取連接池中連接的關鍵代碼,即獲取connections數組中的最后一個元素,獲取到Holder后還需要將其封裝為DruidPooledConnection,這時該連接的connectedTimeMillis會被賦值為當前時間,這個時間在后續的分析中會非常重要。
因為配置了testWhileIdle為true,所以需要進行下面的有效性檢查,獲取該連接的上次活躍時間,得到空閑時間,如果超過30s則做有效性檢查。
long idleMillis = currentTimeMillis - lastActiveTimeMillis; long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis; if (timeBetweenEvictionRunsMillis <= 0) { timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; } if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 // unexcepted branch ) { boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } discardConnection(poolableConnection.holder); continue; } }
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); if (timeMillis >= removeAbandonedTimeoutMillis) { iter.remove(); pooledConnection.setTraceEnable(false); abandonedList.add(pooledConnection); }
同時,由于配置了removeAbandoned為true,所以需要檢查活躍連接是否超時,如果超時就斷開物理連接。下面看一下連接池的回收方法recycle的關鍵代碼
if (phyTimeoutMillis > 0) { long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis; if (phyConnectTimeMillis > phyTimeoutMillis) { discardConnection(holder); return; } } lock.lock(); try { if (holder.active) { activeCount--; holder.active = false; } closeCount++; result = putLast(holder, currentTimeMillis); recycleCount++; } finally { lock.unlock(); }
在對數據庫連接進行回收時,如果連接時間超過了數據庫的物理連接時間(默認8小時)則需要斷開物理連接,否則就調用putLast方法將該連接回收到連接池。
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) { if (poolingCount >= maxActive || e.discard) { return false; } e.lastActiveTimeMillis = lastActiveTimeMillis; connections[poolingCount] = e; incrementPoolingCount(); if (poolingCount > poolingPeak) { poolingPeak = poolingCount; poolingPeakTime = lastActiveTimeMillis; } notEmpty.signal(); notEmptySignalCount++; return true; }
注意上述標紅的地方,回收的這個連接的lastActiveTimeMillis被刷新為當前時間,這個時間也是非常重要的,在后續分析中會用到。
項目關于Atomikos的配置信息,如下所示
從上面的配置可以看出,atomikos連接池的最大連接數是25個,最小連接數是10個,連接最大的存活時間是500s,下面來看一下atomikos的源碼。
private void init() throws ConnectionPoolException { if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": initializing..." ); //如果連接池最小連接數沒有達到就新增數據庫連接 addConnectionsIfMinPoolSizeNotReached(); //開啟維持連接池平衡的線程 launchMaintenanceTimer(); }
以上是Atomikos初始化的部分,先補充數據庫連接池達到最小連接數,然后開啟后臺線程維持連接池的平衡。
private void launchMaintenanceTimer() { int maintenanceInterval = properties.getMaintenanceInterval(); if ( maintenanceInterval <= 0 ) { if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": using default maintenance interval..." ); maintenanceInterval = DEFAULT_MAINTENANCE_INTERVAL; } maintenanceTimer = new PooledAlarmTimer ( maintenanceInterval * 1000 ); maintenanceTimer.addAlarmTimerListener(new AlarmTimerListener() { public void alarm(AlarmTimer timer) { reapPool(); //如果達到了最大的存活時間就移除該連接 removeConnectionsThatExceededMaxLifetime(); //如果沒有滿足最小連接數就新增連接 addConnectionsIfMinPoolSizeNotReached(); //移除超過最小連接數以外的連接 removeIdleConnectionsIfMinPoolSizeExceeded(); } }); TaskManager.SINGLETON.executeTask ( maintenanceTimer ); }
在配置中,maintenanceInterval的值為30,即每個30秒執行一次上述的四個方法,主要看一下removeConnectionsThatExceededMaxLifetime()這個方法。
private synchronized void removeConnectionsThatExceededMaxLifetime() { long maxLifetime = properties.getMaxLifetime(); if ( connections == null || maxLifetime <= 0 ) return; if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": closing connections that exceeded maxLifetime" ); Iterator<XPooledConnection> it = connections.iterator(); while ( it.hasNext() ) { XPooledConnection xpc = it.next(); long creationTime = xpc.getCreationTime(); long now = System.currentTimeMillis(); if ( xpc.isAvailable() && ( (now - creationTime) >= (maxLifetime * 1000L) ) ) { if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": connection in use for more than " + maxLifetime + "s, destroying it: " + xpc ); //如果超過最大的存活時間就銷毀該連接 destroyPooledConnection(xpc); it.remove(); } } logCurrentPoolSize(); }
上述方法遍歷數據庫連接池中的所有連接,如果存活時間超過maxLifetime即500s就銷毀該連接,這時由于連接池中的連接數就小于minPoolSize,所以會立即補充新的連接到連接池中。那么,系統在夜間沒有用戶使用時,Atomikos連接池的運行狀態為:維持最小的連接數10個數據庫連接,當這10個連接超過500s時就會銷毀,再重新創建10個新的數據庫連接,不斷重復這樣的操作。
下面我們開始分析產生錯誤日志的原因,當沒有用戶使用系統時,Druid連接池應該有10個空閑的連接,Atomikos連接池也有10個空閑的連接,這時Atomikos的10個連接達到了最大的生存時間500s,就需要銷毀這些連接,對于Druid來說就是回收連接,調用recycle方法。由于這10個連接應該是500s之前從Druid連接池借出的,所以它們的connectTimeMillis也是500s之前的時間,即物理連接時間肯定小于8小時,可以成功回收到Druid連接池中,同時lastActiveTimeMillis也更新為當前時間,放在connections數組的末尾。
與此同時,Atomikos還需要重新生成10個新的連接,即從Druid連接池獲取10個連接,調用getConnection方法,這時會進行有效性的檢查,又因為lastActiveTimeMillis基本上為當前時間,所以idleMillis肯定比30s小,不需要進行select 1的連接數據庫操作,這樣即使該連接已經失效了還是會借出給Atomikos。每隔500s不斷循環上述操作,并且期間沒有用戶的操作,一旦超過8個小時的Mysql連接時間,Atomikos在使用數據庫連接時就會產生上述日志中的錯誤了。
以上就是“springboot+atomikos+druid數據庫連接失效的原因是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。