ActiveMQ:No operations allowed after statement closed問題及解決辦法


ActiveMQ版本:5.5.1
記錄人:@鄭昀

現象:

系統現象:部分消息發送失敗,失敗頻率不正常。
日志信息:activemq.log 中一直有這樣的錯誤日志:

JDBC FailureNo operations allowed after statement closed.

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after statement closed.

        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)

…………

        at org.apache.activemq.transport.InactivityMonitor.onCommand(InactivityMonitor.java:227)

        at org.apache.activemq.transport.TransportSupport.doConsume(TransportSupport.java:83)

        at org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:220)

        at org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:202)

        at java.lang.Thread.run(Thread.java:662)

Close failed: Already closed.

看上去又是 mq broker 失去了數據庫連接,但代碼仍嘗試在此連接上執行操作,所以 jdbc 直接拋異常。


原因:
ActiveMQ 持久化方案我們選的是 MySQL 。
如果 mq 與 db 之間的數據庫連接,因為數小時的不活動(inactivity),那么 MySQL 就會根據自身的 wait_timeout 參數設置主動斷開連接。這一點在之前的“ActiveMQ:Communications link failure問題以及解決辦法”文章中講過。
只不過,遇到此問題時,
mq client 端報告“com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure”錯誤,
而 mq server 端則報告“ com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after statement closed. ”錯誤。
即原因是,mq broker service 試圖在已關閉的數據庫連接上繼續執行操作
缺陷單 AMQ-2534 甚至報告“

Broker gets stuck with an error about using a closed JDBC statement

”,我們很難說此時 mq broker service 會不會真的“卡住”。
 
解決辦法:
“ActiveMQ:Communications link failure問題以及解決辦法”文章講的一樣,
最簡單辦法,還是根據業務特點,調整 ActiveMQ 所使用的 MySQL 的全局變量 wait_timeout 值,
盡量減少數據庫連接因為 inactivity 而被關閉的幾率 
 
或者在 mq client 里主動捕獲 com.mysql.jdbc.exceptions.jdbc4.CommunicationsException 異常,手動重新連接即可:
  1.         } catch (SQLException sqlEx) {  
  2.             String sqlState = sqlEx.getSQLState();  
  3.            // 08S01就是這個異常的sql狀態。單獨處理手動重新連接即可。  
  4.             if ("08S01".equals(sqlState) || "40001".equals(sqlState))   
 

為什么 Connector/J 不自動重連而非要拋出異常:
下面的文字翻譯自 MySQL 5.5   Connect/J 疑難問題文檔
23.3.15.13: 為什么遇到  communication failure 之后,  Connector/J 不能自己重新連上數據庫,然后重新提交事務呢,而是非要拋出一個異常,即使我用了 autoReconnect 連接字符串屬性?
答:有幾個原因。
第一個,保證事務完整性。MySQL 的幫助文檔上曾說過:“ there is no safe method of reconnecting to the MySQL server without risking some corruption of the connection state or database state information ”。
可以看一下這個案例:
01.conn.createStatement().execute(
  "UPDATE checking_account SET balance = balance - 1000.00 WHERE customer='Smith'");
02.conn.createStatement().execute(
  "UPDATE savings_account SET balance = balance + 1000.00 WHERE customer='Smith'");
03.conn.commit();
考慮一下這個場景:執行完01后,數據庫連接斷了。
如果沒有異常拋出的話,應用程序永遠不知道這個問題,仍繼續執行。
但是第一個事務並沒有提交,所以它回滾了。
如果沒拋異常,數據庫連接自動重連,於是第二個事務被提交了。
從而破壞了事務完整性(transactional integrity)
 
記住,此時 auto-commit 於事無補。當 Connector/J 遇到 communication problem ,你不知道服務器端是否處理了這個事務,有幾種可能:
  • 服務器端沒有接到這個事務,因此什么也沒發生;
  • 服務器端接到了且執行了,但客戶端沒有收到 Response 。
 
第二個原因是,事務里上下文相關數據有可能非常脆弱 ,如:
  • 臨時表;
  • 用戶自定義變量;
  • 服務器端預處理語句(Server-side prepared statements);
如果數據庫連接斷開了,很可能這些數據也消失了;如果此時不拋出異常而是自動重連,你的應用程序很可能跑飛了。
 
總結:
1)silently reconnecting”可能非常不安全,將衍生出很多不可控問題。所以最佳策略是,通知應用程序到底發生了什么,然后由應用開發者決定如何處理
2)mq的生產者頻繁報“com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure”和mq server自己頻繁報“com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after statement closed”,都是因為 mysql  wait_timeout 導致數據庫連接因為不活動而被主動關閉。

 


參考資料:
 


免責聲明!

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



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