ActiveMQ版本:5.5.1
記錄人:@鄭昀
現象:
系統現象:部分消息發送失敗,失敗頻率不正常。
日志信息:activemq.log 中一直有這樣的錯誤日志:
JDBC Failure: No 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 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();
-
- 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 導致數據庫連接因為不活動而被主動關閉。
參考資料: