MyCAT讀寫分離後數據丟失問題

在做讀寫分離時,我們採用了以下方案,在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的前後端連接管理策略。

  1. MyCAT以事務爲單元來請求和使用後端MySQL連接池
  2. 開始事務即可從MySQL連接池中拿連接(後端連接),關閉事務即釋放連接到後端連接池
  3. 釋放MySQL連接回連接池前會在MySQL連接上發一個rollback指令。這會使在應用發一個commit時,經過MyCAT,MySQL收到commit和rollback兩條語句。而應用發一個rollback,經過MyCAT,MySQL會收到rollback和rollback兩條語句
  4. 一個後端MySQL連接上出現事務內的SQL錯誤(比如主鍵衝突)後,應用如果不進行rollback就在別的線程複用前端MyCAT連接,那麼應用新的SQL全部都會失敗。
  5. 如果MySQL連接所在事務處於異常狀態,前端關閉連接,對應MySQL連接會被關閉

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章