溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

分布式事務(wù)的方案有哪些

發(fā)布時間:2021-10-23 15:54:40 來源:億速云 閱讀:113 作者:iii 欄目:web開發(fā)

本篇內(nèi)容介紹了“分布式事務(wù)的方案有哪些”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

分布式事務(wù)的產(chǎn)生

我們先看看百度上對于分布式事務(wù)的定義:分布式事務(wù)是指事務(wù)的參與者、支持事務(wù)的服務(wù)器、資源服務(wù)器以及事務(wù)管理器分別位于不同的分布式系統(tǒng)的不同節(jié)點之上。

分布式事務(wù)的方案有哪些

額~ 有點抽象,簡單的畫個圖好理解一下,拿下單減庫存、扣余額來說舉例:

當(dāng)系統(tǒng)的體量很小時,單體架構(gòu)完全可以滿足現(xiàn)有業(yè)務(wù)需求,所有的業(yè)務(wù)共用一個數(shù)據(jù)庫,整個下單流程或許只用在一個方法里同一個事務(wù)下操作數(shù)據(jù)庫即可。此時做到所有操作要么全部提交  或 要么全部回滾很容易。

分布式事務(wù)的方案有哪些

分庫分表、SOA

可隨著業(yè)務(wù)量的不斷增長,單體架構(gòu)漸漸扛不住巨大的流量,此時就需要對數(shù)據(jù)庫、表做 分庫分表處理,將應(yīng)用 SOA  服務(wù)化拆分。也就產(chǎn)生了訂單中心、用戶中心、庫存中心等,由此帶來的問題就是業(yè)務(wù)間相互隔離,每個業(yè)務(wù)都維護著自己的數(shù)據(jù)庫,數(shù)據(jù)的交換只能進行RPC 調(diào)用。

當(dāng)用戶再次下單時,需同時對訂單庫 order、庫存庫 storage、用戶庫 account  進行操作,可此時我們只能保證自己本地的數(shù)據(jù)一致性,無法保證調(diào)用其他服務(wù)的操作是否成功,所以為了保證整個下單流程的數(shù)據(jù)一致性,就需要分布式事務(wù)介入。

分布式事務(wù)的方案有哪些

Seata 優(yōu)勢

實現(xiàn)分布式事務(wù)的方案比較多,常見的比如基于 XA 協(xié)議的 2PC、3PC,基于業(yè)務(wù)層的TCC,還有應(yīng)用消息隊列 +  消息表實現(xiàn)的最終一致性方案,還有今天要說的 Seata 中間件,下邊看看各個方案的優(yōu)缺點。

2PC

基于 XA 協(xié)議實現(xiàn)的分布式事務(wù),XA 協(xié)議中分為兩部分:事務(wù)管理器和本地資源管理器。其中本地資源管理器往往由數(shù)據(jù)庫實現(xiàn),比如 Oracle、MYSQL  這些數(shù)據(jù)庫都實現(xiàn)了 XA 接口,而事務(wù)管理器則作為一個全局的調(diào)度者。

兩階段提交(2PC),對業(yè)務(wù)侵?很小,它最?的優(yōu)勢就是對使??透明,用戶可以像使?本地事務(wù)?樣使?基于 XA 協(xié)議的分布式事務(wù),能夠嚴格保障事務(wù) ACID  特性。

分布式事務(wù)的方案有哪些

可 2PC的缺點也是顯而易見,它是一個強一致性的同步阻塞協(xié)議,事務(wù)執(zhí)?過程中需要將所需資源全部鎖定,也就是俗稱的  剛性事務(wù)。所以它比較適?于執(zhí)?時間確定的短事務(wù),整體性能比較差。

一旦事務(wù)協(xié)調(diào)者宕機或者發(fā)生網(wǎng)絡(luò)抖動,會讓參與者一直處于鎖定資源的狀態(tài)或者只有一部分參與者提交成功,導(dǎo)致數(shù)據(jù)的不一致。因此,在?并發(fā)性能?上的場景中,基于  XA 協(xié)議的分布式事務(wù)并不是最佳選擇。

分布式事務(wù)的方案有哪些

3PC

三段提交(3PC)是二階段提交(2PC)的一種改進版本  ,為解決兩階段提交協(xié)議的阻塞問題,上邊提到兩段提交,當(dāng)協(xié)調(diào)者崩潰時,參與者不能做出最后的選擇,就會一直保持阻塞鎖定資源。

2PC 中只有協(xié)調(diào)者有超時機制,3PC  在協(xié)調(diào)者和參與者中都引入了超時機制,協(xié)調(diào)者出現(xiàn)故障后,參與者就不會一直阻塞。而且在第一階段和第二階段中又插入了一個準備階段(如下圖,看著有點啰嗦),保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的。

分布式事務(wù)的方案有哪些

雖然 3PC 用超時機制,解決了協(xié)調(diào)者故障后參與者的阻塞問題,但與此同時卻多了一次網(wǎng)絡(luò)通信,性能上反而變得更差,也不太推薦。

TCC

所謂的 TCC 編程模式,也是兩階段提交的一個變種,不同的是 TCC 為在業(yè)務(wù)層編寫代碼實現(xiàn)的兩階段提交。TCC 分別指  Try、Confirm、Cancel ,一個業(yè)務(wù)操作要對應(yīng)的寫這三個方法。

以下單扣庫存為例,Try 階段去占庫存,Confirm 階段則實際扣庫存,如果庫存扣減失敗 Cancel 階段進行回滾,釋放庫存。

TCC 不存在資源阻塞的問題,因為每個方法都直接進行事務(wù)的提交,一旦出現(xiàn)異常通過則 Cancel 來進行回滾補償,這也就是常說的補償性事務(wù)。

原本一個方法,現(xiàn)在卻需要三個方法來支持,可以看到 TCC  對業(yè)務(wù)的侵入性很強,而且這種模式并不能很好地被復(fù)用,會導(dǎo)致開發(fā)量激增。還要考慮到網(wǎng)絡(luò)波動等原因,為保證請求一定送達都會有重試機制,所以考慮到接口的冪等性。

消息事務(wù)(最終一致性)

消息事務(wù)其實就是基于消息中間件的兩階段提交,將本地事務(wù)和發(fā)消息放在同一個事務(wù)里,保證本地操作和發(fā)送消息同時成功。下單扣庫存原理圖:

分布式事務(wù)的方案有哪些

  • 訂單系統(tǒng)向 MQ 發(fā)送一條預(yù)備扣減庫存消息,MQ 保存預(yù)備消息并返回成功 ACK

  • 接收到預(yù)備消息執(zhí)行成功 ACK,訂單系統(tǒng)執(zhí)行本地下單操作,為防止消息發(fā)送成功而本地事務(wù)失敗,訂單系統(tǒng)會實現(xiàn) MQ  的回調(diào)接口,其內(nèi)不斷的檢查本地事務(wù)是否執(zhí)行成功,如果失敗則 rollback 回滾預(yù)備消息;成功則對消息進行最終 commit 提交。

  • 庫存系統(tǒng)消費扣減庫存消息,執(zhí)行本地事務(wù),如果扣減失敗,消息會重新投,一旦超出重試次數(shù),則本地表持久化失敗消息,并啟動定時任務(wù)做補償。

基于消息中間件的兩階段提交方案,通常用在高并發(fā)場景下使用,犧牲數(shù)據(jù)的強一致性換取性能的大幅提升,不過實現(xiàn)這種方式的成本和復(fù)雜度是比較高的,還要看實際業(yè)務(wù)情況。

Seata

Seata 也是從兩段提交演變而來的一種分布式事務(wù)解決方案,提供了 AT、TCC、SAGA 和XA 等事務(wù)模式,這里重點介紹 AT模式。

既然 Seata 是兩段提交,那我們看看它在每個階段都做了點啥?下邊我們還以下單扣庫存、扣余額舉例。

分布式事務(wù)的方案有哪些

先介紹 Seata 分布式事務(wù)的幾種角色:

  • Transaction Coordinator(TC): 全局事務(wù)協(xié)調(diào)者,用來協(xié)調(diào)全局事務(wù)和各個分支事務(wù)(不同服務(wù))的狀態(tài),  驅(qū)動全局事務(wù)和各個分支事務(wù)的回滾或提交。

  • Transaction Manager™: 事務(wù)管理者,業(yè)務(wù)層中用來開啟/提交/回滾一個整體事務(wù)(在調(diào)用服務(wù)的方法中用注解開啟事務(wù))。

  • Resource Manager(RM): 資源管理者,一般指業(yè)務(wù)數(shù)據(jù)庫代表了一個分支事務(wù)(Branch Transaction),管理分支事務(wù)與 TC  進行協(xié)調(diào)注冊分支事務(wù)并且匯報分支事務(wù)的狀態(tài),驅(qū)動分支事務(wù)的提交或回滾。

Seata 實現(xiàn)分布式事務(wù),設(shè)計了一個關(guān)鍵角色 UNDO_LOG  (回滾日志記錄表),我們在每個應(yīng)用分布式事務(wù)的業(yè)務(wù)庫中創(chuàng)建這張表,這個表的核心作用就是,將業(yè)務(wù)數(shù)據(jù)在更新前后的數(shù)據(jù)鏡像組織成回滾日志,備份在 UNDO_LOG  表中,以便業(yè)務(wù)異常能隨時回滾。

第一個階段

比如:下邊我們更新 user 表的 name 字段。

update user set name = '小富最帥' where name = '程序員內(nèi)點事'

首先 Seata 的 JDBC 數(shù)據(jù)源代理通過對業(yè)務(wù) SQL 解析,提取 SQL 的元數(shù)據(jù),也就是得到 SQL  的類型(UPDATE),表(user),條件(where name = '程序員內(nèi)點事')等相關(guān)的信息。

分布式事務(wù)的方案有哪些

第一個階段的流程圖

先查詢數(shù)據(jù)前鏡像,根據(jù)解析得到的條件信息,生成查詢語句,定位一條數(shù)據(jù)。

select  name from user where name = '程序員內(nèi)點事'

分布式事務(wù)的方案有哪些

數(shù)據(jù)前鏡像

緊接著執(zhí)行業(yè)務(wù) SQL,根據(jù)前鏡像數(shù)據(jù)主鍵查詢出后鏡像數(shù)據(jù)

select name from user where id = 1

分布式事務(wù)的方案有哪些

數(shù)據(jù)后鏡像

把業(yè)務(wù)數(shù)據(jù)在更新前后的數(shù)據(jù)鏡像組織成回滾日志,將業(yè)務(wù)數(shù)據(jù)的更新和回滾日志在同一個本地事務(wù)中提交,分別插入到業(yè)務(wù)表和 UNDO_LOG 表中。

回滾記錄數(shù)據(jù)格式如下:包括 afterImage 前鏡像、beforeImage 后鏡像、 branchId 分支事務(wù)ID、xid 全局事務(wù)ID

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

這樣就可以保證,任何提交的業(yè)務(wù)數(shù)據(jù)的更新一定有相應(yīng)的回滾日志。

在本地事務(wù)提交前,各分支事務(wù)需向 全局事務(wù)協(xié)調(diào)者 TC 注冊分支 ( Branch Id) ,為要修改的記錄申請 全局鎖 ,要為這條數(shù)據(jù)加鎖,利用  SELECT FOR UPDATE 語句。而如果一直拿不到鎖那就需要回滾本地事務(wù)。TM 開啟事務(wù)后會生成全局唯一的  XID,會在各個調(diào)用的服務(wù)間進行傳遞。

有了這樣的機制,本地事務(wù)分支(Branch Transaction)便可以在全局事務(wù)的第一階段提交,并馬上釋放本地事務(wù)鎖定的資源。相比于傳統(tǒng)的 XA  事務(wù)在第二階段釋放資源,Seata降低了鎖范圍提高效率,即使第二階段發(fā)生異常需要回滾,也可以快速 從UNDO_LOG 表中找到對應(yīng)回滾數(shù)據(jù)并反解析成 SQL  來達到回滾補償。

最后本地事務(wù)提交,業(yè)務(wù)數(shù)據(jù)的更新和前面生成的 UNDO LOG 數(shù)據(jù)一并提交,并將本地事務(wù)提交的結(jié)果上報給全局事務(wù)協(xié)調(diào)者 TC。

第二個階段

第二階段是根據(jù)各分支的決議做提交或回滾:

如果決議是全局提交,此時各分支事務(wù)已提交并成功,這時 全局事務(wù)協(xié)調(diào)者(TC) 會向分支發(fā)送第二階段的請求。收到 TC  的分支提交請求,該請求會被放入一個異步任務(wù)隊列中,并馬上返回提交成功結(jié)果給 TC。異步隊列中會異步和批量地根據(jù) Branch ID 查找并刪除相應(yīng) UNDO  LOG 回滾記錄。

分布式事務(wù)的方案有哪些

如果決議是全局回滾,過程比全局提交麻煩一點,RM 服務(wù)方收到 TC 全局協(xié)調(diào)者發(fā)來的回滾請求,通過 XID 和 Branch ID  找到相應(yīng)的回滾日志記錄,通過回滾記錄生成反向的更新 SQL 并執(zhí)行,以完成分支的回滾。

注意:這里刪除回滾日志記錄操作,一定是在本地業(yè)務(wù)事務(wù)執(zhí)行之后

分布式事務(wù)的方案有哪些

上邊說了幾種分布式事務(wù)各自的優(yōu)缺點,下邊實踐一下分布式事務(wù)中間 Seata 感受一下。

Seata 實踐

Seata 是一個需獨立部署的中間件,所以先搭 Seata Server,這里以最新的 seata-server-1.4.0  版本為例,下載地址:https://seata.io/en-us/blog/download.html

解壓后的文件我們只需要關(guān)心 \seata\conf 目錄下的 file.conf 和 registry.conf 文件。

Seata Server

file.conf

file.conf 文件用于配置持久化事務(wù)日志的模式,目前提供 file、db、redis 三種方式。


分布式事務(wù)的方案有哪些

file.conf 文件配置

注意:在選擇 db 方式后,需要在對應(yīng)數(shù)據(jù)庫創(chuàng)建 globalTable(持久化全局事務(wù))、branchTable(持久化各提交分支的事務(wù))、  lockTable(持久化各分支鎖定資源事務(wù))三張表。

-- the table to store GlobalSession data -- 持久化全局事務(wù) CREATE TABLE IF NOT EXISTS `global_table` (     `xid`                       VARCHAR(128) NOT NULL,     `transaction_id`            BIGINT,     `status`                    TINYINT      NOT NULL,     `application_id`            VARCHAR(32),     `transaction_service_group` VARCHAR(32),     `transaction_name`          VARCHAR(128),     `timeout`                   INT,     `begin_time`                BIGINT,     `application_data`          VARCHAR(2000),     `gmt_create`                DATETIME,     `gmt_modified`              DATETIME,     PRIMARY KEY (`xid`),     KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),     KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB   DEFAULT CHARSET = utf8;  -- the table to store BranchSession data -- 持久化各提交分支的事務(wù) CREATE TABLE IF NOT EXISTS `branch_table` (     `branch_id`         BIGINT       NOT NULL,     `xid`               VARCHAR(128) NOT NULL,     `transaction_id`    BIGINT,     `resource_group_id` VARCHAR(32),     `resource_id`       VARCHAR(256),     `branch_type`       VARCHAR(8),     `status`            TINYINT,     `client_id`         VARCHAR(64),     `application_data`  VARCHAR(2000),     `gmt_create`        DATETIME(6),     `gmt_modified`      DATETIME(6),     PRIMARY KEY (`branch_id`),     KEY `idx_xid` (`xid`) ) ENGINE = InnoDB   DEFAULT CHARSET = utf8;  -- the table to store lock data -- 持久化每個分支鎖表事務(wù) CREATE TABLE IF NOT EXISTS `lock_table` (     `row_key`        VARCHAR(128) NOT NULL,     `xid`            VARCHAR(96),     `transaction_id` BIGINT,     `branch_id`      BIGINT       NOT NULL,     `resource_id`    VARCHAR(256),     `table_name`     VARCHAR(32),     `pk`             VARCHAR(36),     `gmt_create`     DATETIME,     `gmt_modified`   DATETIME,     PRIMARY KEY (`row_key`),     KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB   DEFAULT CHARSET = utf8;

registry.conf

registry.conf 文件設(shè)置 注冊中心 和 配置中心:

目前注冊中心支持 nacos 、eureka、redis、zk、consul、etcd3、sofa 七種,這里我使用的 eureka作為注冊中心  ;配置中心支持 nacos 、apollo、zk、consul、etcd3五種方式。

分布式事務(wù)的方案有哪些

registry.conf 文件配置

配置完以后在 \seata\bin 目錄下啟動 seata-server 即可,到這 Seata 的服務(wù)端就搭建好了。

Seata Client

Seata Server 環(huán)境搭建完,接下來我們新建三個服務(wù)  order-server(下單服務(wù))、storage-server(扣減庫存服務(wù))、account-server(賬戶金額服務(wù)),分別服務(wù)注冊到eureka。

每個服務(wù)的大體核心配置如下:

spring:     application:         name: storage-server     cloud:         alibaba:             seata:                 tx-service-group: my_test_tx_group     datasource:         driver-class-name: com.mysql.jdbc.Driver         url: jdbc:mysql://47.93.6.1:3306/seat-storage         username: root         password: root  # eureka 注冊中心 eureka:     client:         serviceUrl:             defaultZone: http://${eureka.instance.hostname}:8761/eureka/     instance:         hostname: 47.93.6.5         prefer-ip-address: true

業(yè)務(wù)大致流程:用戶發(fā)起下單請求,本地 order 訂單服務(wù)創(chuàng)建訂單記錄,并通過 RPC 遠程調(diào)用 storage 扣減庫存服務(wù)和 account  扣賬戶余額服務(wù),只有三個服務(wù)同時執(zhí)行成功,才是一個完整的下單流程。如果某個服執(zhí)行失敗,則其他服務(wù)全部回滾。

Seata 對業(yè)務(wù)代碼的侵入性非常小,代碼中使用只需用 @GlobalTransactional 注解開啟一個全局事務(wù)即可。

@Override @GlobalTransactional(name = "create-order", rollbackFor = Exception.class) public void create(Order order) {      String xid = RootContext.getXID();      LOGGER.info("------->交易開始");     //本地方法     orderDao.create(order);      //遠程方法 扣減庫存     storageApi.decrease(order.getProductId(), order.getCount());      //遠程方法 扣減賬戶余額     LOGGER.info("------->扣減賬戶開始order中");     accountApi.decrease(order.getUserId(), order.getMoney());     LOGGER.info("------->扣減賬戶結(jié)束order中");      LOGGER.info("------->交易結(jié)束");     LOGGER.info("全局事務(wù) xid: {}", xid); }

前邊說過 Seata AT 模式實現(xiàn)分布式事務(wù),必須在相關(guān)的業(yè)務(wù)庫中創(chuàng)建 undo_log 表來存數(shù)據(jù)回滾日志,表結(jié)構(gòu)如下:

-- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` (     `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',     `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',     `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',     `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',     `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',     `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',     `log_created`   DATETIME     NOT NULL COMMENT 'create datetime',     `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',     PRIMARY KEY (`id`),     UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB   AUTO_INCREMENT = 1   DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

到這環(huán)境搭建的工作就完事了,完整案例會在后邊貼出 GitHub 地址,就不在這占用篇幅了。

測試 Seata

項目中的服務(wù)調(diào)用過程如下圖:

分布式事務(wù)的方案有哪些

服務(wù)調(diào)用過程

啟動各個服務(wù)后,我們直接請求下單接口看看效果,只要 order 訂單表創(chuàng)建記錄成功,storage 庫存表 used 字段數(shù)量遞增、account 余額表  used 字段數(shù)量遞增則表示下單流程成功。

分布式事務(wù)的方案有哪些

原始數(shù)據(jù)

請求后正向流程是沒問題的,數(shù)據(jù)和預(yù)想的一樣

分布式事務(wù)的方案有哪些

下單數(shù)據(jù)

而且發(fā)現(xiàn) TM 事務(wù)管理者 order-server 服務(wù)的控制臺也打印出了兩階段提交的日志

分布式事務(wù)的方案有哪些

控制臺兩次提交

那么再看看如果其中一個服務(wù)異常,會不會正?;貪L呢?在 account-server 服務(wù)中模擬超時異常,看能否實現(xiàn)全局事務(wù)回滾。

分布式事務(wù)的方案有哪些

全局事務(wù)回滾

發(fā)現(xiàn)數(shù)據(jù)全沒執(zhí)行成功,說明全局事務(wù)回滾也成功了

分布式事務(wù)的方案有哪些

那看一下 undo_log 回滾記錄表的變化情況,由于 Seata  刪除回滾日志的速度很快,所以要想在表中看見回滾日志,必須要在某一個服務(wù)上打斷點才看的更明顯。

分布式事務(wù)的方案有哪些

回滾記錄

“分布式事務(wù)的方案有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI