ActiveMQ版本:5.5.1
記錄人:@鄭昀
現象:
系統現象:部分消息發送失敗,失敗頻率不正常。
日誌信息:activemq.log 中一直有這樣的錯誤日誌:
JDBC Failure: No 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 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 試圖在已關閉的數據庫鏈接上繼續執行操做。
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 異常,手動從新鏈接便可:
- } catch (SQLException sqlEx) {
- String sqlState = sqlEx.getSQLState();
- // 08S01就是這個異常的sql狀態。單獨處理手動從新鏈接便可。
- if ("08S01".equals(sqlState) || "40001".equals(sqlState))
爲何 Connector/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 致使數據庫鏈接由於不活動而被主動關閉。
參考資料: