ActiveMQ:No operations allowed after statement ...

ActiveMQ版本:5.5.1
記錄人:@鄭昀
現象:
系統現象:部分消息發送失敗,失敗頻率不正常。
日誌信息:activemq.log 中一直有這樣的錯誤日誌:

JDBC FailureNo operations allowed after statement closed. html

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

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

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

………… 數據庫


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

        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) tcp

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

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 致使數據庫鏈接由於不活動而被主動關閉。


參考資料:
相關文章
相關標籤/搜索