1. 問題
業務上新增一條訂單記錄,用戶接收到BinLake拉取的MySQL從庫數據消息後,立刻根據消息內的訂單號去查詢同一個MySQL從庫,發現有些時候沒法查到該條數據,等待大約500ms~1000ms後再去查詢數據庫,能夠查詢到該條數據。
注: BinLake爲京東商城數據庫技術部自研的一套訂閱和消費MySQL數據庫binlog的組件,本例所描述的問題是業務方但願根據訂閱的binlog來獲取實時訂單等業務消息。
2. Binlog與內部XA
2.1. XA的概念
XA(分佈式事務)規範主要定義了(全局)事務管理器(TM: Transaction Manager)和(局部)資源管理器(RM: Resource Manager)之間的接口。XA爲了實現分佈式事務,將事務的提交分紅了兩個階段:也就是2PC (tow phase commit),XA協議就是經過將事務的提交分爲兩個階段來實現分佈式事務。
兩階段
1)prepare 階段
事務管理器向全部涉及到的數據庫服務器發出prepare"準備提交"請求,數據庫收到請求後執行數據修改和日誌記錄等處理,處理完成後只是把事務的狀態改爲"能夠提交",而後把結果返回給事務管理器。即:爲prepare階段,TM向RM發出prepare指令,RM進行操做,而後返回成功與否的信息給TM。
2)commit 階段
事務管理器收到迴應後進入第二階段,若是在第一階段內有任何一個數據庫的操做發生了錯誤,或者事務管理器收不到某個數據庫的迴應,則認爲事務失敗,回撤全部數據庫的事務。數據庫服務器收不到第二階段的確認提交請求,也會把"能夠提交"的事務回撤。若是第一階段中全部數據庫都提交成功,那麼事務管理器向數據庫服務器發出"確認提交"請求,數據庫服務器把事務的"能夠提交"狀態改成"提交完成"狀態,而後返回應答。即:爲事務提交或者回滾階段,若是TM收到全部RM的成功消息,則TM向RM發出提交指令;否則則發出回滾指令。
外部與內部XA
MySQL中的XA實現分爲:外部XA和內部XA。前者是指咱們一般意義上的分佈式事務實現;後者是指單臺MySQL服務器中,Server層做爲TM(事務協調者,一般由binlog模塊擔當),而服務器中的多個數據庫實例做爲RM,而進行的一種分佈式事務,也就是MySQL跨庫事務;也就是一個事務涉及到同一條MySQL服務器中的兩個innodb數據庫(目前彷佛只有innodb支持XA)。內部XA也能夠用來保證redo和binlog的一致性問題。
2.2. redo與binlog的一致性問題
咱們MySQL爲了兼容其它非事務引擎的複製,在server層面引入了 binlog, 它能夠記錄全部引擎中的修改操做,於是能夠對全部的引擎使用複製功能; 然而這種狀況會致使redo log與binlog的一致性問題;MySQL經過內部XA機制解決這種一致性的問題。
第一階段:InnoDB prepare, write/sync redo log;binlog不做任何操做;
第二階段:包含兩步,1> write/sync Binlog; 2> InnoDB commit (commit in memory);
固然在5.6以後引入了組提交的概念,能夠在IO性能上進行一些提高,但整體的執行順序不會改變。
當第二階段的第1步執行完成以後,binlog已經寫入,MySQL會認爲事務已經提交併持久化了(在這一步binlog就已經ready而且能夠發送給訂閱者了)。在這個時刻,就算數據庫發生了崩潰,那麼重啓MySQL以後依然能正確恢復該事務。在這一步以前包含這一步任何操做的失敗都會引發事務的rollback。
第二階段的第2大部分都是內存操做,好比釋放鎖,釋放mvcc相關的read view等等。MySQL認爲這一步不會發生任何錯誤,一旦發生了錯誤那就是數據庫的崩潰,MySQL自身沒法處理。這個階段沒有任何致使事務rollback的邏輯。在程序運行層面,只有這一步完成以後,事務致使變動才能經過API或者客戶端查詢體現出來。
下面的一張圖,說明了MySQL在什麼時候會將binlog發送給訂閱者。
理論上來講,也能夠在commit階段完成以後再將binlog發送給訂閱者,但這樣會增大主從延遲的風險。
3. 相關代碼數據庫
其中,在update_binlog_end_pos以後,binlog發送線程就已經能夠讀取最新的binlog發送給訂閱者了。當訂閱者收到這些binlog以後若是process_commit_stage_queue由於系統調度等緣由還未執行完成,那麼訂閱者碰巧在此時發起問題中所描述的查詢,就會發生查詢不到的狀況。
下面咱們看一下process_commit_stage_queue都作了什麼。
在process_commit_stage_queue會分別調用到binlog的commit方法binlog_commit和innodb的commit函數trx_commit_in_memory。服務器
這一步完成以後,在運行時刻事務的變動才能被查詢到。但須要記住,MySQL在binlog落盤成功後就認爲事務的持久化已經完成。
30. 總結
在binlog落盤以後,MySQL就會認爲事務的持久化已經完成(在這個時刻以後,就算數據庫發生了崩潰均可以在重啓後正確的恢復該事務)。可是該事務產生的數據變動被別的客戶端查詢出來還須要在commit所有完成以後。MySQL會在binlog落盤以後會當即將新增的binlog發送給訂閱者以儘量的下降主從延遲。但因爲多線程時序等緣由,當訂閱者在收到該binlog以後當即發起一個查詢操做,可能不會查詢到任何該事務產生的數據變動(由於此時該事務所處線程可能還沒有完成最後的commit步驟)。
若是應用須要根據binlog做爲一些業務邏輯的觸發點,仍是須要考慮引入一些延時重試機制或者從新考慮合適的實現架構。多線程
本文由京東商城數據庫技術部王治提供。架構