天天看點

MySQL binlog 組送出與 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發出送出指令;不然則發出復原指令。

實作:

      MySQL中的XA實作分為:外部XA和内部XA。前者是指我們通常意義上的分布式事務實作;後者是指單台MySQL伺服器中,Server層作為TM(事務協調者),而伺服器中的多個資料庫執行個體作為RM,而進行的一種分布式事務,也就是MySQL跨庫事務;也就是一個事務涉及到同一條MySQL伺服器中的兩個innodb資料庫(因為其它引擎不支援XA)。

1)内部XA的額外功能:XA 将事務的送出分為兩個階段,而這種實作,解決了 binlog 和 redo log的一緻性問題。

      MySQL為了相容其它非事物引擎的複制,在server層面引入了 binlog, 它可以記錄所有引擎中的修改操作,因而可以對所有的引擎使用複制功能。MySQL在4.x 的時候放棄redo的複制政策而引入binlog。但是引入了binlog,會導緻一個問題——binlog和redo log的一緻性問題:一個事務的送出必須寫redo log和binlog,那麼二者如何協調一緻呢?事務的送出以哪一個log為标準?如何判斷事務送出?事務崩潰恢複如何進行?

MySQL通過兩階段送出(内部XA的兩階段送出)很好地解決了這一問題:
第一階段:InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 将復原段設定為Prepared狀态,binlog不作任何操作;
第二階段:包含兩步,1> write/sync Binlog; 2> InnoDB commit (寫入COMMIT标記後釋放prepare_commit_mutex);
以binlog 的寫入與否作為事務送出成功與否的标志,innodb commit标志并不是事務成功與否的标志。
此時的事務崩潰恢複過程如下:
1> 崩潰恢複時,掃描最後一個Binlog檔案,提取其中的xid; 
2> InnoDB維持了狀态為Prepare的事務連結清單,将這些事務的xid和Binlog中記錄的xid做比較,如果在Binlog中存在,則送出,否則復原事務。
通過這種方式,可以讓InnoDB和Binlog中的事務狀态保持一緻。如果在寫入innodb commit标志時崩潰,則恢複時,會重新對commit标志進行寫入;在prepare階段崩潰,則會復原,在write/sync binlog階段崩潰,也會復原      

簡而言之就是:先寫redo log,再寫binlog,并以binlog寫成功為事務送出成功的标志。崩潰恢複是以binlog中的xid和redo log中的xid進行比較,xid在binlog裡存在則送出,不存在則復原。

    MySQL XA分為兩類,内部XA與外部XA;

    内部XA用于同一執行個體下跨多個引擎的事務,由Binlog作為協調者;

    外部XA用于跨多個MySQL執行個體的分布式事務,需要應用層介入作為協調者(崩潰時的懸挂事務,全局送出還是復原,需要由應用層決定,對應用層的實作要求較高);

最常見的内部XA事務存在于binlog與InnoDB存儲引擎之間,進而保證了主從環境的資料一緻性。

2)binlog 組送出:

      上面介紹事務的兩階段送出過程是5.6之前版本中的實作,有嚴重的缺陷。當sync_binlog=1時,很明顯上述的第二階段中的 write/sync binlog會成為瓶頸,而且還是持有全局大鎖(prepare_commit_mutex: prepare 和 commit共用一把鎖),這會導緻性能急劇下降。解決辦法就是在MySQL5.6中引進的binlog組送出。

Binlog Group Commit的過程拆分成了三個階段:

1> flush stage 将各個線程的binlog從cache寫到檔案中; 
2> sync stage 對binlog做fsync操作(如果需要的話;最重要的就是這一步,對多個線程的binlog合并寫入磁盤);
3> commit stage 為各個線程做引擎層的事務commit(這裡不用寫redo log,在prepare階段已寫)。
每個stage同時隻有一個線程在操作。(分成三個階段,每個階段的任務配置設定給一個專門的線程,這是典型的并發優化)      

這種實作的優勢在于三個階段可以并發執行,進而提升效率。注意:prepare階段沒有變,還是write/sync redo log。

(另外:5.7中引入了MTS:多線程slave複制,也是通過binlog組送出實作的,在binlog組送出時,給每一個組送出打上一個seqno,然後在slave中就可以按照master中一樣按照seqno的大小順序,進行事務組送出了。)

題外話:淘寶對binlog group commit進行了進一步的優化,從XA恢複的邏輯我們可以知道,隻要保證InnoDB Prepare階段的redo日志在寫Binlog前完成write/sync即可。是以我們對Group Commit的第一個stage的邏輯做了些許修改,大概描述如下:

1. InnoDB Prepare,記錄目前的LSN到thd中; 
2. 進入Group Commit的flush stage;Leader搜集隊列,同時算出隊列中最大的LSN。 
3. 将InnoDB的redo log write/fsync到指定的LSN  (注:這一步就是redo log的組寫入。因為小于等于LSN的redo log被一次性寫入到ib_logfile[0|1]) #放到flush binlog 之後
4. 寫Binlog并進行随後的工作(sync Binlog, InnoDB commit , etc)
      

也就是将 redo log的write/sync延遲到了 binlog group commit的 flush stage 之後,sync binlog之前。通過延遲寫redo log的方式,顯式的為redo log做了一次組寫入(redo log group write),并減少了(redo log) log_sys->mutex的競争。也就是将 binlog group commit 對應的redo log也進行了 group write. 這樣binlog 和 redo log都進行了優化。

注意:當引入Group Commit後,sync_binlog的含義就變了,假定設為1000,表示的不是1000個事務後做一次fsync,而是1000個事務組。

3)相關參數:

innodb_support_xa:預設為true,表示啟用XA,雖然它會導緻一次額外的磁盤flush(prepare階段flush redo log). 但是我們必須啟用,而不能關閉它。因為關閉會導緻binlog寫入的順序和實際的事務送出順序不一緻,會導緻崩潰恢複和slave複制時發生資料錯誤。如果啟用了log-bin參數,并且不止一個線程對資料庫進行修改,那麼就必須啟用innodb_support_xa參數。

文檔:

http://www.ywnds.com/?p=5798

http://www.ywnds.com/?p=7892 

~~~~~~~~~~~~~~~

萬物之中,希望至美

~~~~~~~~~~~~~~~

繼續閱讀