分布事務
背景:企業業務體量越來越大時候,現有的架構已經不能滿足企業快速需要,軟體架構必然面臨更新,
當下最流行微服務架構,把業務系統功能進行拆分,滿足系統并發性、可用性等。業務拆分面臨着把原來一個完整獨立功能,
變成有兩個或兩個以上應用來完成,應用之間需要彼此通訊完成功能,由于網絡不可靠性以及多應用之間處理失敗情況,
會導緻事務不完整性,出現機率性資料不準确性,分布式事務主要解決這種情況。
關于微服務架構網上資料很多,可自行查閱,本文主要介紹微服務之分布式事務。
什麼是分布式事務 指事務的參與者、支援事務的伺服器、資源伺服器以及事務管理器分别位于不同的分布式系統的不同節點之上。以上是百度百科的解釋,簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的伺服器上,且屬于不同的應用,分布式事務需要保證這些小操作要麼全部成功,要麼全部失敗。本質上來說,分布式事務就是為了保證不同資料庫的資料一緻性。
分布式事務的産生的原因
-
當資料庫單表一年産生的資料超過1000W,那麼就要考慮分庫分表,這時候,如果一個操作既通路01庫,又通路02庫,
而且要保證資料的一緻性,那麼就要用到分布式事務。
- 應用SOA化即業務的服務化
-
比如原來單機支撐了整個電商網站,現在對整個網站進行拆解,分離出了訂單中心、使用者中心、庫存中心。
對于訂單中心,有專門的資料庫存儲訂單資訊,使用者中心也有專門的資料庫存儲使用者資訊,庫存中心也會有專門的資料庫存儲庫存資訊。
這時候如果要同時對訂單和庫存進行操作,那麼就會涉及到訂單資料庫和庫存資料庫,為了保證資料一緻性,就需要用到分布式事務。
-
事務特性(ACID)
- 原子性(A)
- 一緻性(C)
- 隔離性(I)
- 持久性(D)
分布式事務的應用場景
-
支付:經典的場景就是支付了,一筆支付,是對買家賬戶進行扣款,同時對賣家賬戶進行加錢,這些操作必須在一個事務裡執行,
要麼全部成功,要麼全部失敗。而對于買家賬戶屬于買家中心,對應的是買家資料庫,而賣家賬戶屬于賣家中心,對應的是賣家資料庫,
對不同資料庫的操作必然需要引入分布式事務。
- 下單:買家在電商平台下單,往往會涉及到兩個動作,一個是扣庫存,第二個是更新訂單狀态,庫存和訂單一般屬于不同的資料庫,需要使用分布式事務保證資料一緻性。
分布式理論CAP定理
CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出WEB服務無法同時滿足一下3個屬性
- 一緻性(Consistency) : 用戶端知道一系列的操作都會同時發生(生效)
- 可用性(Availability) : 每個操作都必須以可預期的響應結束
-
分區容錯性(Partition tolerance) : 即使出現單個元件無法可用,操作依然可以完成
一個應用至多隻能同時支援上面的兩個屬性,CAP定理在迄今為止的分布式系統中都是适用的。
BASE理論
BASE理論是Basically Available(基本可用)、Soft state(軟狀态)和Eventually consistent(最終一緻性)三個短語的簡寫,
BASE是對CAP中一緻性和可用性權衡的結果,其核心思想是及時無法做到強一緻性,但每個應用都可以根據自身的業務特點,
采用适當的方式使系統達到最終一緻性。
- 基本可用: 基本可用是指分布式系統在出現不可預知故障的時候,允許損失部分可用性
-
弱狀态: 弱狀态也成為軟狀态,和硬狀态相對,是指允許系統中的資料存在中間狀态,并認為該中間狀态的存在不會影響系統的整體可用性,
即允許系統在不同節點的資料副本之間進行資料同步的過程存在延遲。
-
最終一緻性: 最終一緻性強調的是系統所有的資料副本,在經過一段時間的同步後,最終能夠達到一個一緻的狀态。
是以,最終一緻性的本質是需要系統保證最終資料能能夠達到一緻,而不需要實時保證系統資料的強一緻性。
分布式事務方案
- XA兩段和三段送出,利用資料庫協定.
- 柔性事務TCC(Try Confirm Cancel)即分為: 嘗試階段、确認階段、失敗取消階段.
- Saga模型
XA兩段和三段送出方案
- XA協定需要資料庫支援,MySql 5.7以上版本才支援
- XA事務兩段送出(Two phase Commit)
- 第一階段:事務協調器要求每個涉及到事務的資料庫預送出(precommit)此操作,并反映是否可以送出。
- 第二階段:事務協調器要求每個資料庫送出資料。
優點:原理簡單,實作簡單
缺點:同步阻塞、單點問題(協調器單點)、性能較差
- XA三段送出
- 第一階段:CanCommit階段,詢問是否可執行事務送出
- 第二階段:PreCommit階段, 發送預送出
- 第三階段:doCommit階段,确認送出
優點:降低了參與者阻塞範圍,并且能夠在出現單點故障後繼續達成一緻
缺點:如果網絡出現分區,此時協調者所在的節點和參與者無法進行正常網絡通信,這時,參與者依然會進行事務送出,這必然導緻資料不一緻
- 實作者:Mycat(資料庫中間件)、伺服器(Tuxedo/WebLogic/WebSphere)、Seata(中間件)
柔性TCC事務方案
-
TCC(Try-Confirm-Cancel),具體描述是将整個操作分為三步。
兩個微服務間同時進行Try,在Try的階段會進行資料的校驗,檢查,資源的預建立,如果都成功就會分别進行Confirm,
如果兩者都成功則整個TCC事務完成。如果Confirm時有一個服務有問題,則會轉向Cancel,相當于進行Confirm的逆向操作。
-
例如:以線上下單為例,Try階段會去扣庫存,Confirm階段則是去更新訂單狀态,如果更新訂單失敗,
則進入Cancel階段,會去恢複庫存。總之,TCC就是通過代碼人為實作了兩階段送出,不同的業務場景所寫的代碼都不一樣,
複雜度也不一樣,是以,這種模式并不能很好地被複用。
Fescar
- fescar是阿裡巴巴開源的分布式事務中間件,開源後更名Seata
seata源碼核心類
- TM(Transaction Manager)事務管理器,管理全局全局事務
- TC(Transaction Coordinator)事務協調器
- RM(Resource Manager) 資料總管
seata事務
- seata 全局事務預設是讀未送出
- seata 全局事務基于本地事務讀已送出或以上事務
- seata 如果要實作全局讀已送出則支援select for update模式
TM相關
com.alibaba.fescar.tm.api.TransactionalTemplate
RM相關
com.alibaba.fescar.rm.datasource.exec.SelectForUpdateExecutor
com.alibaba.fescar.rm.datasource.ConnectionProxy
com.alibaba.fescar.rm.datasource.exec.AbstractDMLBaseExecutor
com.alibaba.fescar.rm.RMHandlerAT
TC相關
com.alibaba.fescar.server.coordinator.DefaultCoordinator
com.alibaba.fescar.server.coordinator.DefaultCore
com.alibaba.fescar.server.lock.DefaultLockManager
- 關于目前事務通過seata復原到before Image快照,别的線程更新改資料,如何處理這種情況.
- 設計了一個全局的排他鎖,來保證事務間的 寫隔離。
- 本地(Branch)在向TC注冊的時候,把本地事務需要修改的資料table+pks送出到server端申請鎖,拿到全局鎖後,才能送出本地事務
- 全局鎖結構:resourceId + table + pks
- 全局鎖是存在server端branchSession中
-
本地事務【讀已送出】,fescar全局事務【讀未送出】。這是這段話的核心。
我了解的這段話中fescar全局事務讀未送出,并不是說本地事務的db資料沒有正常送出,而是指全局事務二階段commit|rollback未真正處理完(即未釋放全局鎖)。
- 總結來說:全局未送出但是本地已送出的資料,對其他全局事務是可見的【當然在本地事務送出後,本地事務送出前,隔離級别是本地事務的管轄範圍】
-
例子
産品份額有5W,A使用者購買了2萬,份額分支一階段完畢(本地事務份額已經扣除commit),但是在下單的時候異常了。
因為本地事務讀已送出,這時候fescar允許業務通路該條資料,3W,在A使用者的份額分支未復原成功前,對其他使用者可見。
但是其他使用者并不能買該産品,必須等到産品份額復原到5萬,其他使用者才可以操作産品資料。是以看了這個例子 真的有必要做到全局事務讀已送出麼?
- undolog的資料結構
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "172.168.0.38:8091:2018531455",
"branchId": 2018531456,
"sqlUndoLogs": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "t_asset",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_asset",
"rows": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": 12,
"value": "DF001"
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "amount",
"keyType": "NULL",
"type": 3,
"value": ["java.math.BigDecimal", 1]
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "voucher_code",
"keyType": "NULL",
"type": 12,
"value": "e2d1c4512d554db9ae4a5f30cbc2e4b1"
}]]
}]]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_asset",
"rows": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": 12,
"value": "DF001"
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "amount",
"keyType": "NULL",
"type": 3,
"value": ["java.math.BigDecimal", 2]
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "voucher_code",
"keyType": "NULL",
"type": 12,
"value": "e2d1c4512d554db9ae4a5f30cbc2e4b1"
}]]
}]]
}
}]]
}
參考文獻
- Seata官方GitHub位址
- 分布式事務中間件 Fescar - RM 子產品源碼解讀
- 關于開源分布式事務中間件Fescar,我們總結了開發者關心的13個問題
- 分布式事務文章
- TCC分布式事務