阿里分布式事務(wù)框架Seata原理解析

Seata框架是一個(gè)業(yè)務(wù)層的XA(兩階段提交)解決方案。在理解Seata分布式事務(wù)機(jī)制前,我們先回顧一下數(shù)據(jù)庫層面的XA方案。

1. MySQL XA方案

MySQL從5.7開始加入了分布式事務(wù)的支持。MySQL XA中擁有兩種角色:

  • RM(Resource Manager):用于直接執(zhí)行本地事務(wù)的提交和回滾。在分布式集群中,一臺(tái)MySQL服務(wù)器就是一個(gè)RM。
  • TM(Transaction Manager):TM是分布式事務(wù)的核心管理者。事務(wù)管理器與每個(gè)RM進(jìn)行通信,協(xié)調(diào)并完成分布式事務(wù)的處理。發(fā)起一個(gè)分布式事務(wù)的MySQL客戶端就是一個(gè)TM。

XA的兩階段提交分為Prepare階段和Commit階段,過程如下:

  1. 階段一為準(zhǔn)備(prepare)階段。即所有的RM鎖住需要的資源,在本地執(zhí)行這個(gè)事務(wù)(執(zhí)行sql,寫redo/undo log等),但不提交,然后向Transaction Manager報(bào)告已準(zhǔn)備就緒。
  2. 階段二為提交階段(commit)。當(dāng)Transaction Manager確認(rèn)所有參與者都ready后,向所有參與者發(fā)送commit命令。

如下圖所示:


XA.PNG

MySQL XA擁有嚴(yán)重的性能問題。一個(gè)數(shù)據(jù)庫的事務(wù)和多個(gè)數(shù)據(jù)庫間的XA事務(wù)性能對(duì)比可發(fā)現(xiàn),性能差10倍左右。另外,XA過程中會(huì)長時(shí)間的占用資源(加鎖)直到兩階段提交完成才釋放資源。

2. Seata

Seata的分布式事務(wù)解決方案是業(yè)務(wù)層面的解決方案,只依賴于單臺(tái)數(shù)據(jù)庫的事務(wù)能力。Seata框架中一個(gè)分布式事務(wù)包含3中角色:

  • Transaction Coordinator (TC): 事務(wù)協(xié)調(diào)器,維護(hù)全局事務(wù)的運(yùn)行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動(dòng)全局事務(wù)的提交或回滾。
  • Transaction Manager (TM): 控制全局事務(wù)的邊界,負(fù)責(zé)開啟一個(gè)全局事務(wù),并最終發(fā)起全局提交或全局回滾的決議。
  • Resource Manager (RM): 控制分支事務(wù),負(fù)責(zé)分支注冊(cè)、狀態(tài)匯報(bào),并接收事務(wù)協(xié)調(diào)器的指令,驅(qū)動(dòng)分支(本地)事務(wù)的提交和回滾。

其中,TM是一個(gè)分布式事務(wù)的發(fā)起者和終結(jié)者,TC負(fù)責(zé)維護(hù)分布式事務(wù)的運(yùn)行狀態(tài),而RM則負(fù)責(zé)本地事務(wù)的運(yùn)行。如下圖所示:

Seata.PNG

下面是一個(gè)分布式事務(wù)在Seata中的執(zhí)行流程:

  1. TM 向 TC 申請(qǐng)開啟一個(gè)全局事務(wù),全局事務(wù)創(chuàng)建成功并生成一個(gè)全局唯一的 XID。
  2. XID 在微服務(wù)調(diào)用鏈路的上下文中傳播。
  3. RM 向 TC 注冊(cè)分支事務(wù),接著執(zhí)行這個(gè)分支事務(wù)并提交(重點(diǎn):RM在第一階段就已經(jīng)執(zhí)行了本地事務(wù)的提交/回滾),最后將執(zhí)行結(jié)果匯報(bào)給TC。
  4. TM 根據(jù) TC 中所有的分支事務(wù)的執(zhí)行情況,發(fā)起全局提交或回滾決議。
  5. TC 調(diào)度 XID 下管轄的全部分支事務(wù)完成提交或回滾請(qǐng)求。

2.1 為什么Seata在第一階段就直接提交了分支事務(wù)?

Seata能夠在第一階段直接提交事務(wù),是因?yàn)镾eata框架為每一個(gè)RM維護(hù)了一張UNDO_LOG表(這張表需要客戶端自行創(chuàng)建),其中保存了每一次本地事務(wù)的回滾數(shù)據(jù)。因此,二階段的回滾并不依賴于本地?cái)?shù)據(jù)庫事務(wù)的回滾,而是RM直接讀取這張UNDO_LOG表,并將數(shù)據(jù)庫中的數(shù)據(jù)更新為UNDO_LOG中存儲(chǔ)的歷史數(shù)據(jù)。

如果第二階段是提交命令,那么RM事實(shí)上并不會(huì)對(duì)數(shù)據(jù)進(jìn)行提交(因?yàn)橐浑A段已經(jīng)提交了),而實(shí)發(fā)起一個(gè)異步請(qǐng)求刪除UNDO_LOG中關(guān)于本事務(wù)的記錄。

由于Seata一階段直接提交了本地事務(wù),因此會(huì)造成隔離性問題,因此Seata的默認(rèn)隔離級(jí)別為Read Uncommitted。然而Seata也支持Read Committed的隔離級(jí)別,我們會(huì)在下文中介紹如何實(shí)現(xiàn)。

2.2 Seata執(zhí)行流程

下面是一個(gè)Seata中一個(gè)分布式事務(wù)執(zhí)行的詳細(xì)過程:

  1. 首先TM 向 TC 申請(qǐng)開啟一個(gè)全局事務(wù),全局事務(wù)創(chuàng)建成功并生成一個(gè)全局唯一的 XID。

  2. XID 在微服務(wù)調(diào)用鏈路的上下文中傳播。

  3. RM 開始執(zhí)行這個(gè)分支事務(wù),RM首先解析這條SQL語句,生成對(duì)應(yīng)的UNDO_LOG記錄。下面是一條UNDO_LOG中的記錄:

{
    "branchId": 641789253,
    "undoItems": [{
        "afterImage": {
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "GTS"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"
        },
        "beforeImage": {
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "TXC"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"
        },
        "sqlType": "UPDATE"
    }],
    "xid": "xid:xxx"
}

可以看到,UNDO_LOG表中記錄了分支ID,全局事務(wù)ID,以及事務(wù)執(zhí)行的redo和undo數(shù)據(jù)以供二階段恢復(fù)。

  1. RM在同一個(gè)本地事務(wù)中執(zhí)行業(yè)務(wù)SQL和UNDO_LOG數(shù)據(jù)的插入。在提交這個(gè)本地事務(wù)前,RM會(huì)向TC申請(qǐng)關(guān)于這條記錄的全局鎖。如果申請(qǐng)不到,則說明有其他事務(wù)也在對(duì)這條記錄進(jìn)行操作,因此它會(huì)在一段時(shí)間內(nèi)重試,重試失敗則回滾本地事務(wù),并向TC匯報(bào)本地事務(wù)執(zhí)行失敗。如下圖所示:
Seata執(zhí)行流程.PNG
  1. RM在事務(wù)提交前,申請(qǐng)到了相關(guān)記錄的全局鎖,因此直接提交本地事務(wù),并向TC匯報(bào)本地事務(wù)執(zhí)行成功。此時(shí)全局鎖并沒有釋放,全局鎖的釋放取決于二階段是提交命令還是回滾命令。

  2. TC根據(jù)所有的分支事務(wù)執(zhí)行結(jié)果,向RM下發(fā)提交或回滾命令。

  3. RM如果收到TC的提交命令,首先立即釋放相關(guān)記錄的全局鎖,然后把提交請(qǐng)求放入一個(gè)異步任務(wù)的隊(duì)列中,馬上返回提交成功的結(jié)果給 TC。異步隊(duì)列中的提交請(qǐng)求真正執(zhí)行時(shí),只是刪除相應(yīng) UNDO LOG 記錄而已。

提交.png
  1. RM如果收到TC的回滾命令,則會(huì)開啟一個(gè)本地事務(wù),通過 XID 和 Branch ID 查找到相應(yīng)的 UNDO LOG 記錄。將 UNDO LOG 中的后鏡與當(dāng)前數(shù)據(jù)進(jìn)行比較,如果有不同,說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作做了修改。這種情況,需要根據(jù)配置策略來做處理。否則,根據(jù) UNDO LOG 中的前鏡像和業(yè)務(wù) SQL 的相關(guān)信息生成并執(zhí)行回滾的語句并執(zhí)行,然后提交本地事務(wù)達(dá)到回滾的目的,最后釋放相關(guān)記錄的全局鎖。
回滾.png

2.3 Seata隔離級(jí)別

Seata由于一階段RM自動(dòng)提交本地事務(wù)的原因,默認(rèn)隔離級(jí)別為Read Uncommitted。如果希望隔離級(jí)別為Read Committed,那么可以使用SELECT...FOR UPDATE語句。Seata引擎重寫了SELECT...FOR UPDATE語句執(zhí)行邏輯,SELECT...FOR UPDATE 語句的執(zhí)行會(huì)申請(qǐng) 全局鎖 ,如果 全局鎖 被其他事務(wù)持有,則釋放本地鎖(回滾 SELECT...FOR UPDATE 語句的本地執(zhí)行)并重試。這個(gè)過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關(guān)數(shù)據(jù)是已提交的才返回。

SELECT FOR UPDATE.PNG

出于總體性能上的考慮,Seata 目前的方案并沒有對(duì)所有 SELECT 語句都進(jìn)行代理,僅針對(duì) FOR UPDATE 的 SELECT 語句。

3. Seata支持的模式

上文中我們提到的Seata流程只是Seata支持的一種分布式事務(wù)模式,稱為AT模式。它依賴于RM擁有本地?cái)?shù)據(jù)庫事務(wù)的能力,對(duì)于客戶業(yè)務(wù)無侵入性。如圖所示:


AT.PNG

AT模式中業(yè)務(wù)邏輯不需要關(guān)注事務(wù)機(jī)制,分支與全局事務(wù)的交互過程自動(dòng)進(jìn)行。

另外,Seata還支持MT模式。MT模式本質(zhì)上是一種TCC方案,業(yè)務(wù)邏輯需要被拆分為 Prepare/Commit/Rollback 3 部分,形成一個(gè) MT 分支,加入全局事務(wù)。如圖所示:


MT.PNG

MT 模式一方面是 AT 模式的補(bǔ)充。另外,更重要的價(jià)值在于,通過 MT 模式可以把眾多非事務(wù)性資源納入全局事務(wù)的管理中。

4. XA和Seata AT的對(duì)比

XA和Seata.PNG

注:Seata的曾用名為FESCAR。

如圖所示,XA 方案的 RM 實(shí)際上是在數(shù)據(jù)庫層,RM 本質(zhì)上就是數(shù)據(jù)庫自身(通過提供支持 XA 的驅(qū)動(dòng)程序來供應(yīng)用使用)。而 Seata 的 RM 是以二方包的形式作為中間件層部署在應(yīng)用程序這一側(cè)的,不依賴與數(shù)據(jù)庫本身對(duì)協(xié)議的支持,當(dāng)然也不需要數(shù)據(jù)庫支持 XA 協(xié)議。這點(diǎn)對(duì)于微服務(wù)化的架構(gòu)來說是非常重要的:應(yīng)用層不需要為本地事務(wù)和分布式事務(wù)兩類不同場景來適配兩套不同的數(shù)據(jù)庫驅(qū)動(dòng)。

另外,XA方案無論 Phase2 的決議是 commit 還是 rollback,事務(wù)性資源的鎖都要保持到 Phase2 完成才釋放。而對(duì)于Seata,將鎖分為了本地鎖和全局鎖,本地鎖由本地事務(wù)管理,在分支事務(wù)Phase1結(jié)束時(shí)就直接釋放。而全局鎖由TC管理,在決議 Phase2 全局提交時(shí),全局鎖馬上可以釋放。只有在決議全局回滾的情況下,全局鎖 才被持有至分支的 Phase2 結(jié)束。因此,Seata對(duì)于資源的占用時(shí)間要少的多。對(duì)比如下圖所示:

XA鎖資源.PNG
Seata鎖資源.PNG

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容