在做讀寫分離時,我們採用了以下方案,在dataHost上配置了balance=0, 默認禁用讀寫分離。然後只有帶有/*#mycat:db_type=slave*/這樣hint的語句纔會被分發到readHost,後續會逐漸通過添加hint到新的hint語句來來放開更多的流量,希望這樣逐步放開讀流量可以避免一次性全部遷移帶來的影響。
<dataHost name="masterFirst" maxCon="100" minCon="50" balance="0" tempReadHostAvailable="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="-1" slaveThreshold="10">
<writeHost host=mysqla>
<readHost host=mysqlb/>
</writeHost>
....
但是測試發現部分事務會丟失記錄,通過分析發現部分經過MyCAT的事務會被一條rollback回滾。經過分析,是以下模式的事務會被回滾:
set autocommit=0;
INSERT INTO tablea ...
/*#mycat:db_type=slave*/SELECT xx from tableb
COMMIT
在MySQL端看到以上序列會變爲以下模式,在writeHost上
set autocommit=0;INSERT INTO tablea ...
ROLLBACK
在readHost上
SELECT xx from tableb
這樣,插入到表tablea的數據就丟了。而此時,業務服務器,MyCAT服務器和MySQL端都無任何錯誤日誌。
經過分析,發現是MyCAT對以上場景的處理邏輯如下
- MyCAT在一個連接到MySQL writeHost的連接內碰到一個到readHost的語句
- MyCAT釋放writeHost連接回連接池,而釋放連接會發起rollback(這是爲什麼會多一個rollback的原因)
- MyCAT新建到readHost的連接,並執行到readHost的語句
這個邏輯應該是沒有問題的,因爲MyCAT要保證在一個前端事務邊界內(面向應用)任一時刻只能關聯同一個後端連接(到MySQL)。而根源是應用程序做讀寫分離用法上的問題。但是NonBlockingSession.java代碼在釋放並切換連接只產生了一行debug日誌,導致我們在丟失數據的時候在所有地方都找不到錯誤日誌。所以debug級別明顯不利於排查。已經在MyCAT GitHub提醒進行改正。
總結MyCAT的前後端連接管理策略。
- MyCAT以事務爲單元來請求和使用後端MySQL連接池
- 開始事務即可從MySQL連接池中拿連接(後端連接),關閉事務即釋放連接到後端連接池
- 釋放MySQL連接回連接池前會在MySQL連接上發一個rollback指令。這會使在應用發一個commit時,經過MyCAT,MySQL收到commit和rollback兩條語句。而應用發一個rollback,經過MyCAT,MySQL會收到rollback和rollback兩條語句
- 一個後端MySQL連接上出現事務內的SQL錯誤(比如主鍵衝突)後,應用如果不進行rollback就在別的線程複用前端MyCAT連接,那麼應用新的SQL全部都會失敗。
- 如果MySQL連接所在事務處於異常狀態,前端關閉連接,對應MySQL連接會被關閉