分布式事务 -- seata框架AT模式实现原理
Seata AT 模式
- 上一節(jié)中我們提到AT模式是基于XA事務(wù)模型演變過來的,所以他的整體機(jī)制也是一個(gè)改進(jìn)版本的兩階段提交協(xié)議。
- 第一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和鏈接資源
- 第二階段:提交異步化,非常快速完成。回滾通過第一階段的回滾日志進(jìn)行反向補(bǔ)償
- 接下來會(huì)解析整個(gè)執(zhí)行流程中每一個(gè)階段的具體實(shí)現(xiàn) 原來。同時(shí)為了更好的理解AT模式的工作機(jī)制,我們以產(chǎn)品表za_product來描述整個(gè)流程,表結(jié)構(gòu)如下:
AT模式第一階段
- 業(yè)務(wù)流程中執(zhí)行庫存扣減操作的數(shù)據(jù)庫操作時(shí)候,Seata會(huì)基于數(shù)據(jù)源代理對原執(zhí)行的SQL進(jìn)行解析,代理的配置代碼如下。
-
然后將業(yè)務(wù)數(shù)據(jù)在更新前后保存到undo_log 日志表中,利用本地事務(wù)的ACID特性,把業(yè)務(wù)數(shù)據(jù)的更新和回滾日志寫入同一個(gè)本地事務(wù)中進(jìn)行提交,完整的執(zhí)行流程如圖
-
日志表結(jié)構(gòu)如下:
- 假設(shè)AT分支事務(wù)的業(yè)務(wù)邏輯是如下SQl:
- 如上則第一階段的執(zhí)行邏輯如下
- 通過DataSourceProxy對業(yè)務(wù)SQL進(jìn)行解析,得到SQl的類型(UPDATE),表(za_product),條件(where product_code = “GP202103221029”)等相關(guān)信息
- 查詢修改之前的數(shù)據(jù)鏡像,根據(jù)解析得到條件信息生成查詢語句,定位數(shù)據(jù)
- 如上SQl得到產(chǎn)品的對應(yīng)庫存數(shù)據(jù)1000
- 執(zhí)行業(yè)務(wù)SQL:更新這條記錄的count= count-1
- 查詢修改之后的數(shù)據(jù)鏡像,根據(jù)鏡像杰哥通過主鍵定位數(shù)據(jù)
- 得到修改之后的鏡像數(shù)據(jù),此時(shí)count= 999
- 插入回滾日志:把前,后鏡像數(shù)據(jù)以及業(yè)務(wù)SQl相關(guān)的信息組成一條回滾日志記錄,插入undo_log表中,可以在對應(yīng)數(shù)據(jù)庫的undo_log表中獲得數(shù)據(jù),如下圖:
- rollback_info字段是一個(gè)文件類型,包含了回滾的數(shù)據(jù)beforeImage和afterImage,使用官網(wǎng)數(shù)據(jù),如下所示:
- 提交前,向TC注冊分支事務(wù):申請za_product表中主機(jī)值等于1 的記錄的全局鎖
- 本地事務(wù)提交:業(yè)務(wù)數(shù)據(jù)的更新和簽名步驟中生成的undo_log一起提交
- 將本地事務(wù)提交的結(jié)果上報(bào)給TC
- 從AT的第一階段實(shí)現(xiàn)原理看,分支的本地事務(wù)可以在第一階段提交完成后馬上釋放本地事務(wù)所定的資源,這個(gè)是AT事務(wù)和XA最大的不同點(diǎn),XA事務(wù)的兩階段提交,一般鎖定資源后持續(xù)到第二階段的提交或者回滾后才釋放資源,所以實(shí)際上 AT模式降低了鎖定的范圍 ,從而大幅度提升了分布式事務(wù)處理的效率,之所以這樣實(shí)現(xiàn),是因?yàn)镾eata記錄了回滾日志,即使第二階段發(fā)生異常,只需要更具undo_log記錄的數(shù)據(jù)進(jìn)行回滾即可。
AT模式第二階段
- TC接受到所有事務(wù)分支的事務(wù)狀態(tài)匯報(bào)后,決定對全局事務(wù)進(jìn)行提交或者回滾。
事務(wù)提交
- 如果決定是全局提交,說明此事所有分支事務(wù)已經(jīng)完成了提交,只需要清理undo_log日志即可,這也是和XA最大的不同點(diǎn),因?yàn)樵诘谝浑A段各個(gè)分支的本地事務(wù)就已經(jīng)提交了,所以這里并不需要TC來觸發(fā)所有分支事務(wù)的提交,如下圖:
- 如上圖中事務(wù)提交流程:
- 分支事務(wù)收到TC的提交請求后吧請求放入一個(gè)異步隊(duì)列,并馬上返回提交成功的結(jié)果給TC
- 從異步隊(duì)列中執(zhí)行分支,提交請求,批量刪除對應(yīng)的undo_log日志。
- 第一個(gè)步驟中TC并不需要同步知道分支事務(wù)的處理結(jié)果,所以分支事務(wù)才會(huì)采用異步的方式來執(zhí)行,因?yàn)閷τ谔峤徊僮鱽碚f,分支事務(wù)只需要清理undo_log中的日志即可,而即使清理失敗,也不會(huì)對整個(gè)分布式事務(wù)產(chǎn)生任何影響。
事務(wù)回滾
- 整個(gè)全局事務(wù)鏈中,任何一個(gè)事務(wù)分支執(zhí)行失敗,全局事務(wù)都會(huì)進(jìn)入事務(wù)回滾流程。此處回滾根據(jù)undo_log中記錄的鏡像數(shù)據(jù)進(jìn)行補(bǔ)償,如,回滾成功,則數(shù)據(jù)的一致性得到保持。如下流程:
- 所有分支事務(wù)接受到TC的事務(wù)回滾請求后,分支事務(wù)參與者開啟一個(gè)本地事務(wù),執(zhí)行流程如下:
- 通過XID和branch ID查找到相應(yīng)的undo_log記錄
- 數(shù)據(jù)校驗(yàn):拿undo_log中afterImage鏡像數(shù)據(jù)與當(dāng)前業(yè)務(wù)表中數(shù)據(jù)鏡像比較(樂觀鎖),如果不同,說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作修改了,那么事務(wù)不會(huì)回滾。
- 如果afterImage中數(shù)據(jù)和當(dāng)前業(yè)務(wù)表中對應(yīng)的數(shù)據(jù)相同,則根據(jù)undo_log中的beforeImage鏡像數(shù)據(jù)和業(yè)務(wù)SQL的相關(guān)信息生成回滾語句并執(zhí)行:
- 提交本地事務(wù),并把本地事務(wù)的執(zhí)行結(jié)果(即分支事務(wù)回滾的結(jié)果)提交給TC。
關(guān)于事務(wù)隔離性保證
- ACID在事務(wù)特種,有一個(gè)隔離性,指多個(gè)用戶并發(fā)的訪問數(shù)據(jù)庫的時(shí)候,數(shù)據(jù)庫為每一個(gè)用戶開啟的事務(wù)不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離。
- 在AT模式中,當(dāng)多個(gè)全局事務(wù)操作同一張表的時(shí)候,他的事務(wù)隔離的保證是基于全局鎖來實(shí)現(xiàn)的,那么我們針對讀隔離,寫隔離進(jìn)行說明
寫隔離
-
寫隔離是為了在多個(gè)全局事務(wù)針對同一個(gè)表的同一個(gè)字段進(jìn)行更新操作的時(shí)候,避免全局事務(wù)在沒有被提交之前被其他全局事務(wù)修改。寫隔離的主要實(shí)現(xiàn)是,在第一階段本地事務(wù)提交之前,確保拿到了全局鎖。如果拿不到全局鎖,則不能提交本地事務(wù)。并且獲取全局鎖的嘗試會(huì)有一個(gè)范圍控制,如果超出范圍將會(huì)放棄全局鎖的獲取,并且回滾事務(wù),釋放本地鎖。
-
我們用實(shí)際案例解析,假設(shè)兩個(gè)全局事務(wù)tx1,tx2,分別對za_product表的count字段進(jìn)行更新操作,count原始值100.
-
tx1先執(zhí)行,開啟本地事務(wù),拿到本地鎖(數(shù)據(jù)庫級(jí)別的鎖),更新count= count-1= 99。本地事務(wù)提交之前,需要拿到該記錄的全局鎖,然后提交本地事務(wù)并釋放本地鎖。
-
tx2后執(zhí)行,同樣先開啟本地事務(wù),拿到本地鎖,更新count=count-1= 98。本地事務(wù)提交之前,嘗試獲取該記錄的全局鎖(全局鎖由TC控制),由于該全局鎖已經(jīng)被tx1獲取,所以tx2需要等待以重新獲取全局鎖。如果全局事務(wù)整體提交,那么提交時(shí)序圖如下:
- 如果tx1在第二階段執(zhí)行全局回滾,那么tx1需要重新獲得該數(shù)據(jù)的本地鎖,讓后根據(jù)undo_log進(jìn)行事務(wù)回滾。此時(shí),如果tx2仍然在等待該激勵(lì)的全局鎖,同時(shí)持有本地鎖,那么tx1 分支事務(wù)的回滾會(huì)失敗。tx1分支事務(wù)回滾過程會(huì)一直重試,直到tx2的全局鎖獲取超時(shí),放棄全局鎖并回滾本地事務(wù),釋放本地鎖,之后tx1的分支事務(wù)才會(huì)回滾成功。而在整個(gè)過程中,全局鎖在tx1 結(jié)束之前一直被tx1 持有,所以不會(huì)發(fā)生臟寫問題。全局事務(wù)回滾時(shí)序圖如下:
讀隔離
- 我們在數(shù)據(jù)庫學(xué)習(xí)中指定有4種隔離級(jí)別:
- Read Uncommitted:讀取為提交內(nèi)容
- Read Committed:讀取已經(jīng)提交內(nèi)容
- Repeatable read:可重復(fù)讀
- Serializable:可串行化
- 在數(shù)據(jù)庫本地事務(wù)隔離級(jí)別為Read Committed或者以上時(shí)候,Seata AT事務(wù)模式的默認(rèn)全局隔離級(jí)別是Read Uncommitted,在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果,產(chǎn)生臟讀。這在最終一致性事務(wù)模型中是允許存在的,并且在大部分分布式事務(wù)場景中可以接受臟讀。
- 在某些特定場景中要求事務(wù)隔離級(jí)別必須Read Committed,目前Seata是通過SelectForUpdateExecutor執(zhí)行器對SELECT FOR UPDATE語句進(jìn)行代理的,SELECT FOR UPDATE 語句在執(zhí)行時(shí)候回申請全局鎖。如果全局鎖已經(jīng)被其他分支事務(wù)持有,則釋放本地鎖(回滾SELECT FOR UPDATE 語句的本地執(zhí)行)并重試。在這個(gè)過程中,查詢請求會(huì)被“BLOCKING”,直到全局鎖被拿到,也就是讀取的相關(guān)數(shù)據(jù)已經(jīng)提交時(shí)候才返回。
上一篇:分布式事務(wù)框架seata
總結(jié)
以上是生活随笔為你收集整理的分布式事务 -- seata框架AT模式实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone低电量模式在哪里设置
- 下一篇: 数据结构与算法--代码完整性案例分析