iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 28
4
Software Development

30天之從 0 至 1 盡可能的建立一個好的系統 (性能基礎篇)系列 第 28

30-28 之資料庫層擴展難題 - MySQL 分散式事務處理

  • 分享至 

  • xImage
  •  

黑色好看版 - 傳送門


https://ithelp.ithome.com.tw/upload/images/20191013/2008935880kTCS16cb.png

正文開始


本篇文章中,咱們要來說說分散式系統中,最麻煩的部份『 分散式事務 』這一塊,接下來咱們來認真的理一下這個鬼。

本篇分為以下幾個章節 :

  • 分散式事務難題
  • 分散式事務的處理方案 : 2 PC 二階段提交( Two-phase Commit )
  • MySQL XA 事務實現與使用
  • MySQL XA 問題 - 性能

分散式事務難題


首先咱們都知道資料庫有所謂的『 事務 』機制,比較準備的說是事務這個『 單位 』,它當初建立出來是為了解決所謂的 :

確保『 同一組資料庫業務操作 』可以有正確的結果

它們不會因為某項業務的其中一項操作錯誤了,導致整個資料庫的資料不正確 ( A )。

它們不會因為系統固障而導致原本成功修改的資料消失 ( D )。

它們不會因為並行操作,導致資料產生產生無法預期的結果 ( I )。

總而言之,事務在固障與並行的情況下,不會產生所謂的『 資料不一致性 』 ( C )

如果事務可以確保上述事情,那就可以說 :

這個事務有 ACID 的特性

然後咱們在以下三篇文章中,咱們有談到,在 mysql 單機事務的情況下,它們用了以下的機制來確保這些機制 :

  • 原子性 A : undo log
  • 持久性 D : redo log
  • 隔離性 I : 鎖 + mvcc

30-15 之資料庫層的難題 - 單機『 故障 』一致性難題
30-16 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 1 )
30-17 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 2 )

但是注意 ! 上述都是單機 acid 的解法。

那如果換到『 分散式 』的情況怎麼辦呢 ?

這就是咱們接下來要說的東西『 mysql xa 協議 』。

不過先說一下,mysql xa 協議嚴格來說,它只能解決所謂的 :

原子性 A、隔離性 I

持久性這個我還不確定,而一致性這鬼,這東西在分散式的概念又與單機的概念不太相同了,詳細的原因請看看 cap 理論與 base 理論,這裡我不能給準確的答案。

但在開始 xa 協議之前,咱們要先來看看一個東西 2 pc。

分散式事務的處理方案 : 二階段提交( 2 PC Two-phase Commit )


記好 2 pc 它是儘可能讓咱們在分散式的環境下,可以實現『 事務原子性 』,而不是所有的 acid 特性。

2 PC 是為了實現分散式事務的原子性特性

然後比較白話文的說法為 :

2 PC 是為了讓『 分散式事務 』可以達成,要麻全完成,要麻全部未完成

它的基本原理如下 :

  • Phrase 1 : 準備階段
  • Phrase 2 : 提交階段

2 PC 流程

Phrase 1 : 準備階段

接下來咱們第一階段的流程如下圖 1 所示。

  1. 用戶發送個業務進來處理,
  2. 協調者會建立一個分散式事務,這個事務內含對資料庫 a 與 b 的操作,然後會往每資料庫的參與者進行相對應的操作。
  3. 參與者收到請求後,會執行各事務的開始與操作 ( 注意沒有執行 commit 喔 )
  4. 參與者回應給協調者說,我這已經可以『 準備 commit 』了喔

https://ithelp.ithome.com.tw/upload/images/20191013/20089358yFAjXu9QFJ.png
圖 1 : 成功的第一階段

Phrase 2 : 提交階段

上面的第一階段,協調者已經從各參與者收到 :

我們都可以『 準備 commit 』了喔

接下來操作流程如下圖 2 所示。

  1. 協調者和參與者說,你們可以 commit 了
  2. 各參與者實際執行事務 commit。
  3. 各參與者回應成功給協調者。
  4. 當協調者收到『 所有 』參與者成功的回應,才代表這個分散式事務成功結束。

https://ithelp.ithome.com.tw/upload/images/20191013/20089358Avanzn7QaY.png
圖 2 : 成功的第二階段

失敗情況

Phrase 1 : 準備階段

  1. 用戶發送個業務進來處理,
  2. 協調者會建立一個分散式事務,這個事務內含對資料庫 a 與 b 的操作,然後會往每資料庫的參與者進行相對應的操作。
  3. 參與者收到請求後,會執行各事務的開始與操作 ( 注意沒有執行 commit 喔 )
  4. 注意 ! 這時參與者 b 的回應一直沒收到。

https://ithelp.ithome.com.tw/upload/images/20191013/20089358aXCFFSGyyM.png
圖 3 : 失敗的第一階段

Phrase 2 : 回滾階段

  1. 協調者和參與者說,你們全部給我 rollback !
  2. 各參與者實際執行事務 rollback。
  3. 各參與者回應成功給協調者。
  4. 當協調者收到『 所有 』參與者成功的回應,才代表這個事務結束。

https://ithelp.ithome.com.tw/upload/images/20191013/20089358uJNehjN1m1.png
圖 4 : 失敗的第二階段

MySQL XA 事務實現與使用


上述章節就是最基本的 2 pc 流程,而咱們也知道這個流程可以確保咱們的 :

分散式事務 ACID 特性的原子性 A

接下來咱們要來說說 mysql 有支援的 :

XA 協議

而如果是用 mysql 的 xa 協議可以確保 :

分散式事務 ACID 特性的隔離性 I

在這章章開始前先問一下。

xa 協議與 2 pc 是有什麼關係呢 ?

上面的 2 pc 是一種概念與方法,然後 xa 協議是定義出來 2 pc 中『 協調者 』與『 參與者 』各自實際上要做的事情,就像是通訊協議一樣, http 協議定義好,接受方與放送方都要可以解析 http 封包。

首先 mysql 它有支援所謂的『 xa 協議 』,但是它有分為兩種 :

  • 內 xa 協議 : 它主要是用來處理 binlog 與 redo log 的事務。
  • 外 xa 協議 : 就是支援多個資料庫的協議,而這就是咱們在這章節說的分散式事務原子性的實現。

其中咱們的重點是『 外 xa 協議 』,mysql 在這裡的支援為『 xa 協議的參與者 』,所以當有個協調者以 xa 協議發送請求到 mysql 上,它就會進行 2 pc 的處理,就如果 5 所示。

https://ithelp.ithome.com.tw/upload/images/20191013/20089358VfyXxDoUzk.png
圖 5 : mysql 2 pc xa 協議圖

這裡為什麼協調者那要打個問號呢 ? mysql 沒支援嗎 ?

嗯對沒有。

協調者要自已寫程式碼,又或是使用一些資料庫中間件,這些中間件有些會有支援 xa 協調者的功能,下一篇文章會簡單的說明一下。

~ 小知識補充 ~

淺淡一下,為什麼要有『 內 xa 協議 』呢 ?

binlog 在 mysql 中是用來記錄 sql 操作log ,而 redo log 是 innodb 用來記錄修改過的頁 log,反正兩個都是記錄修改資料的 log。這兩個東東詳細的介紹可以看以下兩篇文章。

30-15 之資料庫層的難題 - 單機『 故障 』一致性難題

上面有提到,它倆『 嚴格來說 』都是『 記錄修改資料的 log 』,只是以不同的觀點來記錄,一個是 sql 修改,一個是那個硬碟頁修改。

那這時要如何的保證一致性呢 ? 也就是不會發生一個有 log 而另一個沒 log 的問題。它的解法就是
『 內 xa 協議 』也就是說它也是透過 2 pc 來達成這件事情。

MySQL XA 的使用方式

下面為咱們實際使用 mysql xa 的概念碼,其中下述程式為上圖 5 中的協調者的程式碼。其中注意一下,雖然是用 js 寫的,都這裡全部假設都是同步的就好,反正只是概念碼。

基本上下述程式碼分為幾個階段 :

  1. 兩個資料庫開啟分散式事務 xid1。
  2. 兩個資料庫進行 sql 業務操作。
  3. db_a 的資料庫進行 xa 第一階段,也就是確認『 可否準備提交 』
  4. db_b 的資料庫進行 xa 第一階段,也就是確認『 可否準備提交 』
  5. 當兩個資料庫都確認可提交後,執行 xa 第二階段,提交此分散式事務。
  6. 如果中間有發生失敗,則將此分散式事務進行 rollback。
// 協調者所執行的概念碼

const connA = mysql.createConnection({
  database : 'DB_A'
});
const connB = mysql.createConnection({
  database : 'DB_B'
});


try {
    // (1) 兩個資料庫指定同一個『 分散式事務編號 xid1 』
    connA.query('XA START xid1');
    connB.query('XA START xid1');

    // (2) 兩個資料庫的操作
    connA.query('Update user SET name = Mark WHERE id = 1');
    connB.query('Update user SET name = Ian WHERE id = 2');

    // (3) DB_A 的 2PC Phrase 1 準備階段確認
    connA.query('XA END xid1');
    connA.query('XA PREPARE xid1');

    // (4) DB_B 的 2PC Phrase 1 準備階段確認
    connB.query('XA END xid1');
    connB.query('XA PREPARE xid1');

    // (5) 2PC Phrase 2 提交階段
    connA.query('XA COMMIT xid1');
    connB.query('XA COMMIT xid1');
} catch {

    // (6) 2PC 錯誤時兩個資料庫都 rollback
    connA.query('XA ROLLBACK xid1');
    connB.query('XA ROLLBACK xid1');
}

奇怪這裡還沒提到 xa 協議如何實現 ACID 的隔離性啊 ? 別急等等有

MySQL XA 問題


mysql 根據 2 pc 所實現 xa 協議有個很嚴重的問題那就是 :

性能很毛

使用 mysql xa 性能會非常的差,主要有以下兩個原因 :

性能差原因 1 : 需要等待每個參與者

如果下圖 6 所示,如果每個協調者是同步操作的情況下,那有幾個參與者就要等幾次,而如果是非同步的協調者,那還是要等一個最久的參與者回應,才能往下走,你想想,如果其中一個網路有問題或啥的,等了 5 秒,那就代表這整個分散式事務要 5 秒才能處理完。

https://ithelp.ithome.com.tw/upload/images/20191013/20089358p9TqAwrZ02.png
圖 6 : xa 協議性能差原因 1

性能差原因 2 : MySQL 的事務隔離級別需調整成 SERIALIZABLE

mysql 官網要求如果要使用分散式事務 xa 那就要將隔離級別改為 serializable

However, for a distributed transaction, you must use the SERIALIZABLE isolation level to achieve ACID properties. It is enough to use REPEATABLE READ for a nondistributed transaction, but not for a distributed transaction

而這裡就是實現隔離性的原因 :

mysql xa 協議要求的 serializable 隔離級別,可以確保分散式事務的 ACID 特性中的隔離性

為什麼要改成 serializable 隔離級別呢 ?

先簡單複習一下,咱們 mysql 資料庫預設的 RR 隔離級別的特性如下 :

  • 讀不加鎖,使用 mvcc 版本讀,每次讀取時,只會讀事務開始前的最新版本。
  • 寫加鎖。

然後問題就出在下述圖 7 的這種情況。這個情況是這樣的。

預設欄位值 :
X 資料庫 count = 0
Y 資料庫 price = 10

分散式事務 A 裡面包含兩個不同資料庫 X,Y 的更新操作

X 資料庫操作 => 將欄位 count 更新為 100
Y 資料庫操作 => 將欄位 price 更為為 20

然後咱們還有另一個分散式事務 B 進行以下的操作

分取式事務 B 裡面包含兩個不同資料庫 X,Y 的讀取操作

X 資料庫操作 => 讀取 count 欄位
Y 資料庫操作 => 讀取 price 欄位

最後將結果相乘得出值 = count * price

https://ithelp.ithome.com.tw/upload/images/20191013/20089358e7ABsXRvyY.png
圖 7 : xa 協議如果是 rr 級別的問題

這裡先說一下,為啥圖 7 的正確讀取的結果,會有兩種,這種道理可以想成串行執行,如果兩個事務執行是一個前,一個後,那結果就會有兩種,因為看誰先執行,而這兩種結果是可以接受的,因為它符合 acid 的『 隔離性 』,一個事務的操作,不會影響到另一個事務。

好那先拉回來,問題出在那呢 ?

問題就出在圖 7 紅色框框那的讀取,它會讀到 x 資料庫單機事務已提交的值 count = 100。

這有啥毛病呢 ? 事務不是提交了嗎 ? 嗯『 單機事務 』已經提交了沒錯,但是別忘了『 分散式事務 』還沒提交。

咱們在說一致性問題時,有提到一個難題那就是『 髒讀 』。

髒讀代表某個事務會讀取到其它事務『 未提交 』的值

而這也代表它沒有符合 :

acid 的隔離性,因為 a 分散式事務的操作影響到 b 分散式事務的讀取

以分散式事務角度來看,它已經發生髒讀的情況,因為分散式事務 b 讀取到分散式事務 a 的『 未提交 』值。

就是因為這種情況,所以 mysql 要使用 xa 協議就一定要將它隔離級別 serializable。當級別改成它後,操作特性就變成如下 :

  • 讀加鎖 ( 不管 mvcc 版本 )
  • 寫加鎖

所以如果是 serializable 的隔離級別,結果變的圖下圖 8 所示,這樣就會讀取到正確的結果。這就是為什麼使用 xa 一定要 serializable 級別的原因。

xa 一定要使用 serializable 級別的原因在於,rr 級別在分取式事務情況,會發生髒讀的問題

https://ithelp.ithome.com.tw/upload/images/20191013/200893580k5oFjKC0f.png
圖 8 : xa 協議如果是 serializable 級別的結果

而由於這個 serializable 級別要求,使得分散式事務變成串行處理,也相對確保了 ACID 的隔離性

~ 小備註 ~
如果不熟悉資料庫隔離級別的友人,可以參考以下兩篇文章。

30-16 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 1 )
30-17 之資料庫層的難題 - 單機『 並行 』一致性難題 ( 2 )

結論與心得


本篇文章中咱們提到了某些方面我覺得算是分散式系統中的大難題 :

如果讓分散式事務執行 acid 的特性

而這裡咱們將了一個最基本的方式 :

2 PC + MySQL XA 協議

不過記好這個方案性能很毛。還有個重點,嚴格來說這個 2 pc 是只完成了 acid 中的 a 與 i 這個兩個特性,而致於其它兩個特性在分散式環境要如何解呢 ? 不好意思可能要下一次鐵人賽才知道了……

最後這裡淺淡一下實務上的使用,首先,我在實務上到現在沒有看到太多真的有在使用 mysql xa 協議來處理分散式事務的這東西,最主要的原因在於性能這個問題。只要用了 mysql xa 所有資料庫都要調整成 serializable 級別,那資料庫的性能幾乎可以說是差了 10 倍左右,這影響非常的巨大。

那現在大部份公司如何的處理分散式事務這一塊呢 ? 我知道有幾個名詞例如消息隊列處理或 tcc 等這些方法,但在實務上台灣有沒有公司用我就不知道呢。

說實話,就算是在咱公司也都沒有處理這塊 ( 我所知道的部份 ),雖然咱們有分庫,但是那是為了『 業務或維護 』而分,不是為了性能,咱們在使用時大部份 95 % 的時間還是單庫操作,真有多庫操作也都是讀的情況。

不過這裡也只是我自已看到的東西,假設有看到有台灣公司有處理這塊的,真想跪請他們分享一下是如何處理這塊鬼問題的。

順到說一下這也讓我有點好奇,微服務這一塊,它這裡是如何處理的呢 ? 這或需是未來另一個可以深入探討的方向呢。

參考資料



上一篇
30-27之資料庫層的擴展 - 分區表
下一篇
30-29 之資料庫層擴展中間件 - MyCAT 的淺淺談
系列文
30天之從 0 至 1 盡可能的建立一個好的系統 (性能基礎篇)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
西撒
iT邦新手 5 級 ‧ 2020-03-08 23:33:43

Phrase 1 : 準備階段,其中一個參與者失敗了,第二階段進行 rollback

在 Phrase 2 : 回滾階段,如果還是失敗了,沒收到 ack

那該怎麼辦呢?

馬克 iT邦研究生 3 級 ‧ 2020-03-09 16:03:34 檢舉

如果在第二階段有兩台資料庫 A 與 B 要收到 rollback 發生以下情況時,會這樣。

A 收到 rollback => 正常執行 rollback (沒啥毛病)。
B 沒收到 rollback => 分散式事務失敗,用戶就收到失敗訊息,而 B 資料庫的單機事務,會因為資料庫連線有異常 terminated (因為收不到 rollback 指令有可能是因為網路或資料庫掛了),而自動進行 rollback。(不過聽說不同應用與資料庫好像有不同處理,參考看看這篇stackoverflow,我也不確定是與否)

順到說一下,這裡還有很多 2pc 的異常問題可以說,而這也牽扯出為什麼會有 3pc 或之後的一些分散式事務解,然後又可以牽扯出為啥 mysql xa 之支援 2pc 的問題。隨便想一下大概可以寫個 5 篇…… 希望下次有空時可以寫寫。

0
Rig
iT邦新手 5 級 ‧ 2022-11-22 05:44:08

這邊小小提醒一下,是 phase (階段) 不是 phrase (措辭) 喔

我要留言

立即登入留言