日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

分布式事务 -- seata框架AT模式实现原理

發(fā)布時(shí)間:2023/12/4 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分布式事务 -- seata框架AT模式实现原理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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)如下:
CREATE TABLE `za_product` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`product_code` varchar(100) NOT NULL,`name` varchar(100) NOT NULL,`count` varchar(128) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

AT模式第一階段
  • 業(yè)務(wù)流程中執(zhí)行庫存扣減操作的數(shù)據(jù)庫操作時(shí)候,Seata會(huì)基于數(shù)據(jù)源代理對原執(zhí)行的SQL進(jìn)行解析,代理的配置代碼如下。
@Beanpublic DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource){return new DataSourceProxy(druidDataSource);}
  • 然后將業(yè)務(wù)數(shù)據(jù)在更新前后保存到undo_log 日志表中,利用本地事務(wù)的ACID特性,把業(yè)務(wù)數(shù)據(jù)的更新和回滾日志寫入同一個(gè)本地事務(wù)中進(jìn)行提交,完整的執(zhí)行流程如圖

  • 日志表結(jié)構(gòu)如下:

-- 注意此處0.7.0+ 增加字段 context CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  • 假設(shè)AT分支事務(wù)的業(yè)務(wù)邏輯是如下SQl:
update za_product set count = count-1 where product_code = "GP202103221029"
  • 如上則第一階段的執(zhí)行邏輯如下
    • 通過DataSourceProxy對業(yè)務(wù)SQL進(jìn)行解析,得到SQl的類型(UPDATE),表(za_product),條件(where product_code = “GP202103221029”)等相關(guān)信息
    • 查詢修改之前的數(shù)據(jù)鏡像,根據(jù)解析得到條件信息生成查詢語句,定位數(shù)據(jù)
select id,product_code,count name from za_product where product_code = "GP202103221029"
  • 如上SQl得到產(chǎn)品的對應(yīng)庫存數(shù)據(jù)1000
  • 執(zhí)行業(yè)務(wù)SQL:更新這條記錄的count= count-1
  • 查詢修改之后的數(shù)據(jù)鏡像,根據(jù)鏡像杰哥通過主鍵定位數(shù)據(jù)
select id,product_code,count name from za_product where id= 1
  • 得到修改之后的鏡像數(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ù),如下所示:
{"branchId": 2034562134,"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" }
  • 提交前,向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í)行:
update za_product set count = 1000 where id = 1
  • 提交本地事務(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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。