seata-分佈式事務與seata

分佈式事務與 Seata

分佈式事務

分佈式事務是個現實中很常見的現象,平常的跨行轉帳就是一個很典型的分佈式事務。java

現實中,每一個銀行各自管理各自的帳戶,在執行跨行轉帳時,須要確保轉出帳戶扣費正確,轉入帳戶增長正確的金額。在電子渠道上操做看着很簡單,其後臺須要執行分佈式事務的處理流程有不少步驟,若是帳戶不平,還須要進行人工對帳,中間涉及到一系列的制度和機制。這裏暫且不涉及電子交易的安全可信的問題,只討論分佈式事務。算法

轉帳的數字形式上無非是一系列的電子信號,可是它是用戶的財富的事實表明,其轉移有法律上的意義,銀行不能隨意改動(理論上),任何的修改都是須要有事實依據的,是獲得用戶受權的。正如經典數據庫的說法,銀行的數據庫存儲的是一系列事實。sql

在早期,沒有銀行間共同信任的中間機構時,銀行要與其它銀行轉帳時,必需要在對方開設一個銀行專屬的帳戶,轉帳時變成了從這個專屬帳戶交易到普通帳戶,而後銀行間再定時進行結算。這種方式會致使 n 個銀行之間相互轉帳時,須要 n^2 個專屬帳戶,試想 1 萬間銀行的狀況,因此基本沒法在大量銀行間進行維護。數據庫

而後銀行間共同組織起共同信任的中間機構,如中國銀聯和 SWIFT ,你們都經過中間機構進行轉帳(仍是會有須要銀行之間直接對接的狀況的)。在轉帳時,可能會出現各類各樣的狀況:安全

1. 扣款失敗
2. 扣款成功,通知失敗
3. 扣款成功,通知成功,入帳失敗
4. 扣款成功,通知成功,用戶要求撤回
5. ...

這個場景下,涉及到多方參與者,爲保證整個交易事實的正確一致,必須知足多方都成功或撤銷,就是一個分佈式的事務。服務器

爲實現分佈事務的問題,有一些協議:兩階段提交、三階段提交、事務補償、saga 等等。網絡

兩階段提交(2pc)

2pc 認爲,既然各方參與者都沒法肯定對方的狀態,那麼引入一個事務協調者來統一安排參與者的操做吧。多線程

1. 協調者首先向全部參與者發一個準備消息 prepare ,參與者在本地進行預處理事務,並報告可否執行
2.1 協調者收到全部參與者的確認消息,那麼就發一個 global_commit 消息,全部參與者進行提交
2.2 協調者收到有任意的參與者預處理失敗,或者參與者超時,就發一個 global_rollback 消息,全部參與者取消

但 2pc 沒有想到的是,這個世界充滿了不肯定性,任何的節點均可能會出現問題:架構

  1. 同步阻塞,全部參與者都是事務阻塞型,公共資源會處於阻塞狀態
  2. 單點故障,協調者發出 prepare 後異常,那麼全部參與者都在處理本地事務掛起狀態
  3. 不一致,部分參與者節點在 global_commit 前異常,這部分事務沒有提交,致使局部數據不一致
  4. 全局事務狀態沒法肯定,一旦協調者異常,即便出現新的協調者,也是沒法肯定以前的事務狀態

因而,在 2pc 的基礎上,又推出了三階段提交(3pc)app

三階段提交(3pc)

2pc 沒法肯定協調者狀態,在 3pc 時爲協議者也引入超時機制;針對同步阻塞和不一致問題,增長一個準備階段。整個過程爲成 can_commit、pre_commit、do_commit(abort) 3個階段。

1. 協調者發 can_commit,參與者檢查,類型於 prepare
2. 協調者發 pre_commit,參與者開始本地事務,寫 undo 日誌
3.1 全部參與者成功 pre_commit,協調者發 do_commit,參與者提交,清理 undo
3.2 任意參與者失敗或超時,協調者發 abort,參與者回滾並釋放資源
3.3 協調者超時,參與者默認 commit

若是出現同步阻塞,那麼會在 2 出現失敗,全局回滾。參與者默認 commit ,使得只要 pre_commit 階段成功,那麼分佈式事務就能默認提交。

可是這裏還存在數據不一致問題,若是 pre_commite 階段,部分參與者成功,部分失敗,而成功參與者沒有收到後續的 abort ,會默認提交,形成不一致。

分佈式一致性算法,目前好像基本上都是 Paxos 的變種,它實現的也不是所有都一致,是基於少數服從多數的原則,在使用場景上有所區別,多見於存儲系統中。zookeeper 就是基於 paxos 的實現。

CAP 原理

CAP 原理描述的在分佈式中,3種屬性:一致性 consistency, 可用性 avaliablity, 分區容錯 partition tolerance,沒法 3個同時知足。

以轉帳爲例,A 系統扣款,通知 B 系統進行入帳,有可能出現網絡中斷致使通知失敗的狀況,A 能夠採起:

1. 放棄可用性,禁止對這個帳戶進行的任何操做,直到獲得 B 確認
2. 放棄強一致性,後臺任務繼續通知 B 系統,同時容許對帳戶的繼續操做

可是沒法放棄分區容錯的,在分佈式系統中,必然是有多個節點的,也就是 P 是必然,所以一個分佈式系統要麼以 CP 、要麼以 AP 爲設計目標。在不一樣的應用中,會採用不一樣的策略,像 zookeeper 用的 cp ,而 eureka 以 ap 爲目標。

通常而言,實現分佈式強一致的代價會比較高,並會致使整個服務暫時不可用。試想下,一筆轉帳尚未獲得確認前,全部的其它的動帳操做都不可執行,怎麼向脾氣火爆的客戶解析系統不支持繼續操做,只由於對方系統恰好在發出了交易消息後掛了!固然,也不能讓轉帳的事實丟失,扣款後對方系統沒有入帳,要作些補償操做,實現最終一致性。

所謂的最終一致性,即系統是某個時間段存在不一致性,可是過了此段時間後會是一致的(經過一些補償操做)。最終一致性只在分佈式下有意義,其在本地也是強一致性的,不然沒法作補償。爲此,又提出了 BASE 模型

BASE 模型

BASE 模型從另外的角度來看待分佈式系統,提出 3 方面的想法:

1. 基本可用 Basically avaliable
2. 軟狀態(柔性事務) Soft-state
3. 最終一致 Eventually consitence

BASE 模型不追求經典數據庫系統的 ACID ,而是和分佈式系統的特色結合起來,在可用性、一致性上爭取平衡。

  1. 基本可用

    容許分佈式系統的若干節點出現不可預測的問題,在設計時應該在部分節點出現問題時,系統能夠以低一點的效率運行,系統其它部分還能提供基本的服務。

    在這樣的設計約束下,基本上沒法追求強一致性的目標,不然就會把全部的服務鎖死。

  2. 軟狀態(柔性事務)

    容許數據存在中間狀態,在中間狀態下暫時不一致。中間狀態爲最終一致服務,可提交或撤銷。

  3. 最終一致

    系統必須能在某個時間內實現最終一致性。對於同一個邏輯全局事務,各分支事務必須都提交或都撤銷。通常分紅幾種:

    1. 因果一致性
         A 節點操做後致使 B 節點操做,那麼 B 操做時得到的狀態是 A 的結果狀態,和 A 無關的操做則無此要求
     2. 讀本身已寫
         同一個節點老是能讀到本身已處理後的結果狀態,屬於特殊的因果一致性,也可理解爲單節點本身的本地一致性
     3. 會話一致性
         同一個會話中,客戶端老是能讀到本身更新的最新狀態,這是要求比較高的因果一致性
     4. 單調讀一致性
         在得到某個讀取狀態以後,全部的讀取的狀態都不能取得比此狀態更舊的狀態,也就是讀取的結果只能愈來愈新
     5. 單調寫一致性
         系統保證來自同一節點的寫操做老是按順序執行

要留意到一點,並不能保證全部節點的寫一致性。時間的肯定在分佈式系統中是比較複雜的問題,在單點系統中,系統只有一個時間源,操做的前後順序是比較肯定的,即便是多線程的操做中,時間相差通常不會超過 100 毫秒級(從取當前時間值到用此時間進行寫操做);但分佈系統時間中,各個節點都要依賴於本身的時間源,即便使用 ntp 進行定時校對,也會比單節點系統的時間差別大,網絡的延遲更加放大了時間的差別,通常在收到交易的時候要分別記上客戶端時間和服務器時間,並拋棄時間差別過大的交易。

好了,有了這些基礎能夠去看看 seata 框架了。

seata 框架

seata 是 alibaba 開源的一套分佈式事務方案,同時支持 TCC/AT/SAGA/XA 等多種分佈式事務模型。它由 alibaba 的 fescar 升級而來,併合並了一些其它的技術。

seata 適合於微服務架構,其雲版本 GTS 是 aliyun 企業分佈式應用服務 EDAS 的一個組件。

基本概念

TC transaction coordinator 事務協調器

維護全局事務的運行狀態、協調和驅動全局事務的提交或回滾

TM transaction manager 事務管理器

控制全局事務的邊界,負責開啓全局事務,並最終發起全局提交或回滾動

RM resource manager 資源管理器

控制子事務,負責子事務開啓和狀態管理,接收 TC 的指令,驅動子事務的提交和回滾動。

GT global transaction 全局事務

在全局範圍內執行的事務

BT branch transaction 分支事務(本地事務)

在本地範圍內執行的事務

seata 分紅兩個部分,seata server 和 seata client。

seata server 是一個獨立的可運行組件,擔當 TC 的角色,它能夠註冊到微服務的註冊中心,被 client 發現和調用。

AT 模式

AT automatic transaction 自動事務模式,AT 模式基於 XA 演化,是 2PC 的改進,須要本地數據庫支持的 ACID 。

須要分支事務支持 ACID,每一個分支事務獨立地完成工做,基本流程:

1. prepare 準備
2. 提交或回滾

基本條件:1) 本地數據庫支持 ACID ;2) java 應用使用 jdbc 驅動訪問數據庫

seata 基於 jdbc 分析 sql 條件,從而肯定 sql 的執行範圍,在執行前、執行後分別得到數據的鏡像數據,並寫入到 undo_log 中,從而在失敗時進行回滾。

機制

階段1:提交業務數據 + 建立 undo 日誌,而後釋放鎖和鏈接資源;

階段2:
結果爲 commit ,那麼異步提交各個主事務;
結果爲 rollback ,進行補償,使用階段1的回滾日誌進行回滾;

寫隔離

在階段1提交前,先要得到一個全局鎖,若是階段1獲取全局鎖失敗,則不能提交,能夠進行屢次嘗試,超時以後取消本地事務。

兩個寫事務時,單個流程以下:

tx1: 
啓動本地事務,本地鎖
update m=m-100 where id=1
獲取全局鎖
本地事務提交
釋放本地鎖
全局提交
釋放全局鎖

沒有髒寫問題

讀隔離

本地數據庫讀已提交,無鎖狀況下,全局讀未提交(若是改成讀已提交,須要使用 select for update,至關於啓動一次寫事務)

select for update 得到一個全局鎖,從而實現讀已提交

執行過程

對 product 表執行 update product set name = 'GTS' where name = 'TXC'

過程以下:

階段1

  1. 預備 sql,經過 sql 得到類型爲 update,表名 product,經過 where 子句查出數據行,並取出數據

    進行數據定位

    select id, name, sice from productt where name = 'TXC'

    經過表結構,可得知 id 爲鍵,並獲得執行前數據鏡像

    id  |  name | since
     1   |  TXC  | 2014
  2. 執行 update 語句,而後再次查詢獲得更新後的數據鏡像

    查詢語句

    select id, name, since from product where id = 1;

    結果

    id  | name  | since
     1   | GTS   | 2014
  3. 建立一個 undo_log (回滾日誌),標記事務 id ,執行前、執行後結果

    日誌格式

    {
         "branchId": 641789253,
         "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"
         }],
         "xid": "xid:xxx"
     }
  4. 提交前,經過 TC 得到 product 表全局鎖,主鍵爲 1

  5. 提交本地事務,更新 product 表和 undo_log 表,向 TC 彙報執行結果

階段2

若是節點收到 TC 的 rollback 通知,那麼經過 xid 和 子事務 id 找到 undo_log 日誌,比較當前狀態和 afterImage 的數據,若是一致則進行回滾

若是不一致,就要通知 TC

收到 commit 通知,那麼把請求直接放到一個隊列,立刻返回,再異步在本地逐個刪除隊列的 undo_log 日誌。

undo_log 格式

與數據庫類型相關,不一樣數據庫有點不同,但基本構成都是 branch_id ,xid ,beforeImage/afterImage (rollback_info),時間 等。

TCC 模式

AT 藉助了數據庫的 ACID 特性,但分佈式事務中,部分步驟不具有 ACID 能力,如長時間的人工操做、庫存清點等,TCC (try-comfirm-cancel) 模式比較
適合這種場景。

TCC 須要一個微服務提供 3 種操做:

1. try 資源預留,扣減資源並建立 undo_log
2. commit 成功提交,刪除 undo_log
3. cancel 取消,根據 undo_log 進行回滾

流程

TM -> TC : 啓動 GT,獲得 xid
服務 -> RM : prepare
RM -> TC : 註冊 branch
RM -> TC : branch 狀態報告
TC -> RM : commit / rollback

SAGA 模式

SAGA 是對長耗時事務的解決方案,好比一些交易步驟須要到人工參與的審覈,或須要調用外部服務時,就是 saga 的合適場景。它與 TCC 不一樣,沒有 prepare 動做。 見 https://www.jianshu.com/p/e4b662407c66?from=timeline&isappinstalled=0

每一個 SAGA 由一系列的子事務組成,爲 T1,T2..Tn ,每一個 Ti 對應一個補償動做 Ci,用於撤銷 Ti 形成的結果。

執行時,執行 T1,T2..Tn,若是 Ti 出錯,那麼就要進行倒過來執行補償動做,以撤銷前 i-1 個結果(棧方式)。

每一個本地事務都是原子的,可是全局不能實現讀寫隔離。

用於與外部系統集成、或其它組件不支持 TCC 的狀況。

相關文章
相關標籤/搜索