一文教你迅速解決分散式事務XA一致性問題

克勞德同學發表於2017-09-01

近日,騰訊雲釋出了分散式資料庫解決方案(DCDB),其最明顯的特性之一就是提供了高於開源分散式事務XA的效能。大型業務系統有著使用者多、併發高的特點,在這方面,集中式資料庫(單機資料庫)的效能很難支援,因此主流的網際網路公司往往採用分散式(架構)資料庫,物理上利用更多的低端裝置,邏輯上對大表水平拆分支撐業務的需要。

雖然分散式資料庫能解決效能難題,但事務一致性(Consistency)的問題,卻很難在分散式資料庫上得到解決。

分散式事務老大難題,資料一致難以實現

眾所周知,一個事務所做的更新,分散式資料庫系統內部多個獨立的資料節點完成(每個節點的本地事務是這個全域性事務的一個事務分支),在這樣一個全域性事務提交期間,有可能某些事務分支無法成功提交。

針對這一問題,雖然業內早已存在理論解決方案——二階段提交協議(簡稱2PC),並延伸出分散式事務(簡稱XA)的解決方案。但業內卻少有工程化實現且大規模應用的案例。而騰訊雲分散式資料庫DCDB,卻已在內部業務中應用多年。


(圖:二階段提交演算法)

目前DCDB已應用在騰訊內部90%以上的交易、計費業務,並且三一重工(樹根互聯)、匯通天下(G7)、閱文集團(起點/創世中文網等)、微眾銀行、和泰人壽、威富通等都在該產品。

騰訊雲首發分散式資料庫XA,支援MySQL 5.7

騰訊雲分散式資料庫DCDB,是基於騰訊金融級資料庫(公司內部代號TDSQL)雲化改造而來的相容MySQL協議的分散式資料庫。現如今,騰訊雲DCDB已經正式在MySQL 5.7(percona分支)協議上支援分散式事務XA,並已在騰訊雲公有云、金融雲釋出供開發者使用。開發者可以透過申請DCDB例項,並在初始化後,連線例項執行如下sql進行初始化:

 

MySQL> xa init;

Query OK, 0 rows affected (0.03 sec)


注意:初始化xa前,請開啟強同步複製能力,另外該sql會建立xa.gtid_log_t,使用者在後續使用中萬勿對其進行任何操作。。

為更好的支援分散式事務,DCDB還新增了SQL命令:

1)  SELECT gtid(),獲取當前分散式事務的gtid(事務的全域性唯一性標識),如果該事務不是分散式事務則返回空;

    gtid的格式:

    ‘閘道器id’-‘閘道器隨機值’-‘序列號’-‘時間戳’-‘分割槽號’,例如 c46535fe-b6-dd-595db6b8-25


2)  SELECT gtid_state(“gtid”),獲取“gtid”的狀態,可能的結果有:

    a)  “COMMIT”,標識該事務已經或者最終會被提交

    b)  “ABORT”,標識該事務最終會被回滾

    c)  空,由於事務的狀態會在一個小時之後清楚,因此有以下兩種可能:

            1) 一個小時之後查詢,標識事務狀態已經清除

            2) 一個小時以內查詢,標識事務最終會被回滾


3) 運維命令:

    xa recover:向後端SET傳送xa recover命令,並進行彙總

    xa lockwait:顯示當前分散式事務的等待關係(可以使用dot命令將輸出轉化為等待關係圖)

    xa show:當前閘道器上正在執行的分散式事務

Python為例,可以對轉賬業務進行如下編碼:   

    db = pyMySQL.connect(host=testHost, port=testPort, user=testUser, password=testPassword, database=testDatabase)

    cursor = db.cursor()

    try:

        cursor.execute("begin")


        #為一個賬戶Bob的餘額減1

        query = "update t_user_balance SET balance = balance - 1  where user='Bob' and balance>1)

        affected = cursor.execute(query)

        if affected == 0: #餘額不足,回滾事務

            cursor.execute("rollback")

            return


        #為一個賬戶John的餘額加1

        query = "update t_user_balance SET balance = balance + 1  where user='John')

        cursor.execute(query)


        # 為了安全起見,建議在這裡執行‘SELECT gtid()’獲取當前事務的id值,便於後續跟蹤事務的執行情況


        #提交事務

        cursor.execute("commit")

    except pyMySQL.err.MySQLError as e:

        # 發生故障,回滾事務

        cursor.execute("rollback")

分散式事務的好處在於會大大降低應用開發難度,因為在某些不支援XA的資料庫中,需要業務系統透過特殊並且巧妙的設計,而非利用資料庫來解決事務中資料不一致等問題。這種對應用開發者的技術水平要求很高,越是複雜的業務系統,越會增加開發成本和技術門檻,這是業內大多數開發者面對分散式資料庫時,只能望而卻步的主要原因。

騰訊雲DCDB XA關鍵實現方案

1DCDB架構介紹

騰訊雲DCDB整個叢集架構簡圖如下圖,MySQL採用主從節點配置(也叫作主備)一套主從節點叫做SET,在每一個SET外配置閘道器(TProxy),形成一個物理分片(Shard)。

DCDB後端是MySQL(或其分支版本)資料庫,目前騰訊雲公有云釋出支援XA的版本是基於MySQL 5.7.17percona分支)。

2、閘道器(TProxy)與XA

閘道器是用於接收請求並與後端MySQL建立連線的網路模組。閘道器可以用兩種模式工作,一種稱為noshard,此模式下閘道器不處理/不解析SQL語句,透明轉發請求和應答。另一種模式稱為shard(分散式,即支援自動水平分表)模式下,TProxy會解析SQL並轉發到不同的資料分片。

在實現XA之前,閘道器不允許在一個事務中向多個SET傳送DML語句。因為未實現二階段提交(2PC)時,事務採用一階段提交,如果分散式中某一個SET提交失敗了或回滾了,那麼這個分散式事務就處於不一致的狀態。

(閘道器的工作方式)

二階段提交中需要的事務管理器TM)。為了解決容災、簡化架構,騰訊雲DCDBTM實現在TProxy中,而DCDB的閘道器是一個無狀態的模組,透過這一架構,DCDB XA可以支援:

1、分散式事務對業務透明,相容單機事務語法(start transaction/commit/rollback/savepoint)

2、每個閘道器都可以獨立接受和處理事務請求,且無需與其他閘道器進行協調節點故障不丟失事務。

3、允許顯式事務中多條語句分別發給多個分片。

4、閘道器無需持久狀態,無需容災,可以隨時經由排程叢集退出或加入叢集,且效能可以擴充套件。

5、支援autocommit下單條語句寫訪問多個分片等。

DCDB閘道器還允許以流式處理方式執行group byorder by,流式處理讓這類操作變得非常方式非常高效;閘道器還支援兩個Shard使用shardkey(分表鍵)做等值連線,以及使用shardkey的子查詢。

未來,騰訊雲還計劃支援分散式JOINSparksql、二級分割槽等高階功能,相容更多MySQL高階語法。

3、強同步與XA

由於騰訊雲DCDB預設採用強同步複製,即主從節點資料完全一致,因此XA事務也遵循強同步的邏輯,即需等待從機確認資料同步後,才給業務以應答(commit)。基於強同步,在以下兩種異常情況下,DCDB XA可輕鬆應對

1、主節點故障時,已確認事務資料不會丟失:主節點故障那麼擁有最新資料和binlog的從機就被選為主節點,這其中的資料也包括所有已經向使用者確認完成提交的事務的資料。

2、原主節點恢復後重新加入叢集,未確認事務自動閃回:原主節點恢復重新接入叢集,它將作為從機執行,此時他可能存留多餘的已提交事務(此時事務並未得到強同步同步確認,即原備機並沒有相關資料),那麼這些事務會被閃回。雖然這些事務可能已經在原主節點的MySQL內部完成提交,但由於強同步機制,他並不會向客戶端返回commit語句,這意味著仍被視為一個未完成的事務。因此,這些事務的閃回了也並沒有破壞資料庫的ACID屬性。這裡值得說的是,閃回flashback是基於binlog生成做逆操作,它與資料庫回滾並不同rollback,閃回可以做DDL操作。

騰訊雲DCDB的強同步為騰訊金融級資料庫自研的一項能力,效能比官方半同步大幅提高,幾乎等於非同步複製效能,騰訊雲DCDB在騰訊內部應用多年,未發生過一起因為主從切換、故障帶來的資料誤差。而且,從效能上,也撐住了騰訊公司各類大型運營活動如紅包、各類遊戲大型推廣等海量併發,其主要原因是強同步採用非同步提交/等待方式,且不佔用資料庫工作執行緒。

4、併發控制與隔離級別

為了達到資料一致性和效能的平衡,分散式事務的關鍵是資料庫隔離控制。XA的隔離級別最高可以達到serializable(完全序列化),該級別將不存在幻讀的問題serializable級別可以透過設定SET global tx_isolation='serializable'來對DCDB所有物理分片(和其上承載的MySQL資料庫)進行設定。當然,也可以透過調整隔離級別以調整資料庫例項效能,理論上,Read Uncommitted效能最高,但可能存在髒讀、幻讀的情況。

隔離解別

髒讀

不可重複讀

幻讀

Read Uncommitted

Y

Y

Y

Read Committed

N

Y

Y

Repeatable(default)

N

N

Y

Serializable

N

N

N

(ANSI/ISO定義的SQL-92標準定義的四種隔離級別)

5、分散式事務處理演算法

前面講到,騰訊雲DCDB的閘道器在shard模式下已經能夠解析SQL語句,騰訊雲在閘道器上實現TM以使得XA最具效率。為此,我們在閘道器中實現TM中的協調器(coordinator),並在閘道器中維護每個XA的狀態,記錄好每個XA寫入的SET,然後在提交階段做兩階段提交即可,大致流程如下:

1、閘道器在執行一個事務的insert/update/delete語句時,會記錄這個語句修改了哪個SET

2SET時會傳送一個XA START在這個SET上面啟動事務分支;(注:XA事務開始時,並不確認事務將以哪種提交方式執行,因此總是以xa start來開啟一個事務)

3、檢測是否影響SET個數≤1,若是,則直接做一階段提交(xa commit one phase)。

4、影響SET個數≥2,則改為做兩階段提交

1)閘道器首先傳送xa prepare gtid’ 給參與的SET(大於等於2SET

2)SET接受到xa prepare應答ok(表示成功確認)

3)收到成功確認後,寫入XA對應的commit log,再傳送xa commit gtid’參與SET

4)如果有SET返回了錯誤,或者寫入commit log失敗,那麼閘道器傳送 xa rollback gtid’ 給相關SET,這樣這個全域性事務就實現了回滾。

騰訊雲DCDBcommit log是在SET中儲存,這個步驟是批次完成的——閘道器後臺執行緒會彙集正在提交的分散式事務然後在獨立的連線和事務中完成對每個SET的寫入,並且每個事務的commit log只寫入一個SET中,因而這個開銷並沒有顯著增加事務的提交耗時或者降低TPS而且,依賴騰訊雲DCDB已有的強同步和容災特性,只要XA成功寫入了commit log,就意味著資料已經寫入從機。

雖然絕大多數的XA事務可以正常執行。但極少數的異常情況還是會影響整個叢集穩定性,因此,騰訊雲設計了agent(監控模組),在故障後繼續協助完成本地MySQL上面prepared事務的提交,即agent會解析commit log,並根據異常處理本地仍然處於prepared的事務資料;如果commit log上面沒有事務的提交決定的話,agent也會回滾超時未被提交的prepared本地事務。

雖然在MySQL 5.55.6等版本早已實現XA,但這兩個版本相對於5.7仍然有效能不足,因此騰訊雲目前只在公有云上基於5.7.17支援XA版本。如今,騰訊雲在MySQL perconaMariaDB等分支中做了大量最佳化和相關bug修復(部分已經提交到社群修復patch或開源),未來騰訊雲還將繼續致力於新特性的開發和相關Bug的修復,為眾多有需要的企業,提供更好的分散式資料庫支援。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31483116/viewspace-2144409/,如需轉載,請註明出處,否則將追究法律責任。

相關文章