.net core 微服務之 分佈式事務

概念

什麼是事務

事務是由一組操作組成的一個工作單元。

事務特性

原子性:事務內部的一組操作要麼同時成功,要麼同時失敗

隔離性:不同事務之間是互相不影響的

一致性:事務內部一組操作,各自操作產生的結果數據,要能夠保證都是預期的狀態

持久性:事務內部一組操作,各個操作產生的數據要能夠持久的效應

什麼是分佈式事務

分佈式事務就是一組服務操作的集合

例如:在分佈式系統或者微服務系統內,完成一個任何,需要涉及到多個服務來共同完成,這一組服務操作組成的集合,就是分佈式事務

分佈式事務類型

1.  不同服務不同數據庫

​2.  不同服務相同數據庫

3.  相同服務不同數據庫

 

 

 

 

事務分類和分佈式事務演化

剛性事務

分爲兩階段提交和三階段提交

剛性事務適用於-----相同服務不同數據庫-----的場景

剛性事務---兩階段

事務參與者: 所有需要操作到的服務都是事務參與者

事務協調者: 統一協調參與者的事務

 

第一階段: 準備階段  prepare : 事務協調者向所有事務參與者詢問是否準備好,並且參與者準備好之後返回yes

第二階段: 提交階段  commit :事務協調者向所有事務參與者提交,並且參與者提交成功之後返回ack

 

 

 

剛性事務---三階段

在兩階段的基礎上在最前面新增了一個確認階段

第一階段:確認階段 canCommit  :事務協調者向所有事務參與者確認服務是否正常,並且參與者確認好之後返回yes

第二階段: 準備階段  preCommit : 事務協調者向所有事務參與者詢問是否準備好,事務參與者寫提交日誌,並且參與者準備好之後返回ack

第三階段: 提交階段  doCommit :事務協調者向所有事務參與者提交,並且參與者提交成功之後返回havaCommit

 

 

缺點:

1. 同步阻塞: 如果其中一個階段其中一個服務出現問題,會導致其他服務阻塞,所以性能低

2. 數據不一致: 如果提交階段其中一個服務提交失敗,未能返回ack ,那麼就會造成數據的不一致

3. 單點故障: 如果事務協調者出現異常,會造成所有的服務阻塞

雖然會有這麼多缺點,但是都是在微服務之間異常或者通信異常導致的,同一個服務就不會存在這個問題,所以

剛性事務適用於-----相同服務不同數據庫-----的場景

 

柔性事務

就是不完全遵守事務4特性的分佈式事務-----主要體現在一致性(不完全一直,最終一致性)

基於CAP理論以及BASE理論

Base理論核心思想 :理論的核心思想就是:我們無法做到強一致,但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性

可查詢操作:服務操作具有全局唯一標識,操作唯一確定的時間

冪等操作:重複調用多次產生的業務結果與調用一次產生的結果相同,一是通過業務操作實現冪等性,二是系統緩存所有請求與處理結果,最後是檢測到重複請求之後,自動返回之前的處理結果。

柔性事務可以分爲

1. 同步事務(http,rpc) :Tcc分佈式事務  和 Saga分佈式事務

2. 異步事務(消息隊列MQ)

 

柔性事務之TCC

操作數據庫會產生三個數據狀態:1、未確認狀態,2、確認狀態,3、取消未確認狀態

Tcc分爲子事務和全局事務

子事務:每個要操作的服務都是一個子服務, 分爲三個階段:

1、Try階段  : 所有要操作的服務中都要先執行一個try階段,把需要添加的數據進行預添加,此時會將改數據標記爲未確認的狀態

2、Confirm階段 :所有子服務的try階段成功之後,執行Confirm階段,把需要添加的數據進行添加,此時會將改數據標記爲確認狀態

3、Cancel階段:如果在第二階段出現某個子服務異常,會通知其他服務進行回滾,將第一步Try階段添加的預添加數據進行刪除

全局事務:多個子事務的集合, 操作到的第一個服務就會產生一個全局事務,每次同子服務交互都會帶上全局服務的ID進行關聯

 

 

 

優點

1.解決了跨服務的業務操作原子性問題,例如組合支付,訂單減庫存等場景非常實用
2.TCC的本質原理是把數據庫的二階段提交上升到微服務來實現,從而避免了數據庫2階段中鎖衝突的長事務低性能風險。
3.TCC異步高性能,它採用了try先檢查,然後異步實現confirm,真正提交的是在confirm方法中。

缺點

1.對微服務的侵入性強,微服務的每個事務都必須實現try,confirm,cancel等3個方法,開發成本高,今後維護改造的成本也高。
2.爲了達到事務的一致性要求,try,confirm、cancel接口必須實現等冪性操作。
(定時器+重試)
3.由於事務管理器要記錄事務日誌,必定會損耗一定的性能,並使得整個TCC事務時間拉長,建議採用redis的方式來記錄事務日誌。

TCC每個子事務會自己執行,不會造成阻塞,不會造成性能消耗過大,就算其中一個子事務造成了異常,會產生一個重試機制不停的重試,從而達到最終一致性

所以TCC適用於微服務-----(  不同服務不同數據庫、不同服務相同數據庫)-----的場景

 

柔性事務之Saga

子事務:每個要操作的服務都是一個子服務, 分爲兩個階段:

第一個階段(Ti): 直接執行業務階段, 直接像數據庫中添加數據,添加成功後向協調器返回 成功

第二個階段(Ci): 直接取消階段,第一個執行階段所有服務都執行成功了就不會執行第二個取消階段,當第一個階段其中某個服務失敗了就會執行子事務中的Ci取消階段,然後向協調器發送命令,協調器向其他執行成功的子服務也發送命令

 

 

 

優點

1、避免服務之間的循環依賴,因爲saga協調器會調用saga參與者,但參與者不會調用協調器

2、集中分佈式事務編排

3、降低參與者的複雜性

4、回滾更容易管理

Saga模式的一大優勢是它支持長事務。因爲每個微服務僅關注其自己的本地原子事務,所以如果微服務運行很長時間,則不會阻止其他微服務。這也允許事務繼續等待用戶輸入。此外,由於所有本地事務都是並行發生的,因此任何對象都沒有鎖定。

缺點

協調器集中太多邏輯的風險

Saga模式很難調試,特別是涉及許多微服務時。此外,如果系統變得複雜,事件消息可能變得難以維護。Saga模式的另一個缺點是它沒有讀取隔離。例如,客戶可以看到正在創建的訂單,但在下一秒,訂單將因補償交易而被刪除

Saga結合了剛性事務和TCC一些優勢,但是相對於TCC沒有那麼複雜,相對於剛性事務中事務協調器做了集羣, 每個子事務會自己執行,不會造成阻塞,不會造成性能消耗過大,就算其中一個子事務造成了異常,會產生一個重試機制不停的重試,從而達到最終一致性

所以TCC適用於微服務-----(  不同服務不同數據庫、不同服務相同數據庫)-----的場景

 

使用saga的 ServiceComb Pack 框架構建微服務

Saga Pack 架構是由alpha和omega組成,其中:alpha充當協調者的角色,主要負責對事務進行管理和協調。

服務端omega是微服務中內嵌的一個agent,負責對網絡請求進行攔截並。客戶端向alpha上報事務事件。saga數據庫,存儲事務參與者的事務數據(mysql,postsql)

 

 

 環境搭建

搭建 Alpha 服務端環境

1. 安裝  Java Jdk 

2. 下載 ServiceComb Pack , 下載後解壓

3.  安裝 Mysql 或者  PostgreSQL 數據庫。 由於ServiceComb Pack 只支持兩種數據庫,我這裏就用的mysql 

4. 官網下載 mysql 的數據庫Java連接驅動  ,我這裏用的是 mysql-connector-java-8.0.15.jar

5. 在解壓好的 ServiceComb Pack 根目錄下面創建一個 plugins 的文件夾,文件夾中將第四步的驅動放在這個文件夾中

 

 

6. 在mysql 中創建一個名爲 saga 的數據庫, 在當前目錄使用CMD ,然後運行命令,注意修改數據庫地址信息和賬號密碼

java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username={賬號}" -D"spring.datasource.password={密碼}" -jar alpha-server-0.5.0-exec.jar

如圖,運行成功,表示 alpha 服務端搭建成功了,數據庫也會生成相關表結構

 

 

 

 可以看到生成的表不僅有Saga ,還有 Tcc, 說明ServiceComb Pack 同時還兼容Tcc協議,這裏我們用不到Tcc,直接忽略Tcc的表 

搭建 Omega 環境

由於Nuget源中沒有引入Omega,所以我們只有下載源碼之後,把源碼引入我們的文件當中。源碼中還是測試項目示例,可以看看使用

   Github 上下載  Omega c# 源碼

 

分佈式事務示例

簡單的使用分佈式事務

1.  在我們 項目中引入源碼中Src中的項目

2. 在我們需要用到分佈式事務項目中引入兩個  Servicecomb.Saga.Omega.Core  和   Servicecomb.Saga.Omega.AspNetCore  

 

 

 

3. 注入服務

services.AddOmegaCore(option =>
            {
                option.GrpcServerAddress = "localhost:8080"; // 1、協調中心地址
                option.InstanceId = $"ConsulApi-1";// 2、服務實例Id
                option.ServiceName = $"ConsulApi";// 3、服務名稱
            });

4. 分佈式事務開始的接口方法上面打上特性  [SagaStart] 。  我這裏懶沒有用多個服務,就用的本身這個consulapi運行了多個不同端口的實例模擬多個服務

[SagaStart]
        [HttpGet("AddUser")]
        public async Task<IActionResult> AddUser()
        {
            //獲取當前端口,根據端口號的不同進行不同的業務
            var localPort = Request.HttpContext.Connection.LocalPort;
            if (localPort == 5001)
            {
                HttpClient client = new HttpClient();
                client.BaseAddress = new Uri("http://localhost:5002");
                await client.GetAsync("/api/User/AddUser");

                HttpClient client2 = new HttpClient();
                client2.BaseAddress = new Uri("http://localhost:5003");
                await client2.GetAsync("/api/User/AddUser");
                return Ok("添加成功");
            }
            else if (localPort == 5002)
            {
                _userService.Create(new UserInfo()
                {
                    Name = "Darcy",
                    Age = 18
                });
            }
            else if (localPort == 5003)
            {
                _cityService.Create(new City()
                {
                    CityName = "成都市"
                });
            }

            return Ok("添加失敗");
        }

5. 在每個操作數據庫的方法上面打上 特性  [Compensable(nameof(補償方法名稱))]  。  注意這裏有個坑,補償方法一定不要用public ,否則會報未將對象引用到實例

private string connStr = "server=localhost;port=3306;user=root;password=hua3182486;database=fcbsaga;SslMode=none;";
        [Compensable(nameof(Delete))]
        public void Create(UserInfo model)
        {
            using (var conn=new MySqlConnection(connStr))
            {
                conn.Execute($"insert into UserInfo(name, sex) values(@name,@age)",model);
            }
        }
        void Delete(UserInfo model)
        {
            using (var conn = new MySqlConnection(connStr))
            {
                conn.Execute($"delete from  UserInfo where id ='{model.Id}'");
            }
        }

 

6.  添加程序集信息文件  AssemblyInfo.cs

using Servicecomb.Saga.Omega.Abstractions.Transaction;


[module: SagaStart]
[module: Compensable]

 

7. 重點: Nuget 引入 MethodDecorator.Fody ,然後重新生成項目,會生成 FodyWeavers.xml 文件 。 fody會在生成IL時將一些代碼自動生成進去,這裏目的是爲了在執行方法前後去執行  SagaStart  和  Compensable ,方便協調器監控各個子事務,去判斷是否執行補償機制。(官方也沒有看到這個操作,花了兩天時間踩坑,羊了個羊)

Nuget:  MethodDecorator.Fody

 

運行之後,可以看到數據庫裏面添加了幾條記錄,從數據中可以看出,在SagaStart開始時會生成一個全局事務ID,然後會把全局事務ID傳給 Compensable 中,Compensable也會生成一個子事務ID,和全局事務ID關聯起來,這裏也就是執行了saga第一個階段TI ,如果子事務中出現了異常,就會通知全局事務ID,從而去觸發第二個補償階段。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章