ASP-NET Core - CAP 實現分佈式事務

需要注意的是標題中的 CAP 不是指的 CAP 理論,而是園區大神楊曉東實現的框架,CAP 框架基於本地消息表用最終一致性實現分佈式事務。

本地消息表

首先我們考慮一個場景,在將用戶信息更改後,需要發送一條消息到消息隊列、緩存或是寫入到其他庫中。這個過程涉及到一個本地庫與 MQ、本地庫與 Cache 或是本地庫與其他庫兩者之間的事務問題,不能用簡單的數據庫事務控制了。

這種分佈式事務下,常用的解決方案有 2PC、3PC 等強一致性保證的,也有 TCC、Sagas 模型、本地消息表、內嵌本地消息表的 MQ 等最終一致性保證的。

而在很多異步場景下,允許系統存在短暫的不一致,只需達到最終一致,比起強一致性那種剛性事務,採用柔性事務,在很多場景下更有利於我們去實現。

執行過程

在使用 CAP 框架前,先熟悉下作爲分佈式事務解決方案之一的本地消息表工作過程。

  1. 消息發起方 (如圖左側部分) 和消息接收方(如圖右側部分),先額外建一套消息表,用來記錄及跟蹤消息內容及狀態。

  2. 當有請求到消息發起方時,處理完業務邏輯發佈消息將業務數據和消息數據一同提交到本地表中,此時爲本地事務。

  3. 本地事務沒有問題後,將消息發送到 MQ 傳遞給消息消費方。如果消息發送失敗,會進行重試發送。

  4. 消息消費方,接收並處理消息,完成自己的業務邏輯,此時爲消息消費方本地事務,如果本地事務完成,則更改接收消息的狀態,更改本地,如果處理失敗,那麼可再次重試執行。

  5. 最終,左側事務與右側事務達到最終一致。

CAP 框架

CAP 是一個在分佈式系統中(SOA,MicroService)實現事件總線及最終一致性(分佈式事務)的一個開源的 C# 庫,具有輕量級,高性能,易使用等特點。

  1. 第一個包 DotNetCore.CAP 爲必須要安裝的。

  2. 可以依據消息隊列的不同選擇用 RabbitMQ、Kafka 或是 AzureServiceBus 等。

  3. 根據服務使用的數據庫情況選擇需要將本地消息表落庫,可以選擇 SqlServer、MySql、PostgreSql、MongoDB 等,或是直接使用內存存儲,方便快速實踐。

場景案例

依照 EShopOnContainers 中的一張圖來實現一個例子,用戶更新用戶信息,將更新的部分通過事件發送到消息隊列中,下游的購物車和訂單服務偵聽到消息,更改買家信息。

在此基礎上行,設計三個上下文,並分別集成 CAP, 藉助 RabbitMQ 作爲消息隊列,對於 UserService、BasketService 和 OrderService, 都直接使用了數據庫(當然可以不僅限於數據庫)。

服務建立

項目創建

開始建立幾個服務,新建空白解決方案,依次建立三個 WebApi 項目,並移除默認的控制器。

簡單設計下,在三個服務中創建三個 DbContext,對應三個獨立的數據庫。

安裝 Nuget 包

在三個服務中均安裝完如下選中的包,此次 Demo 中爲方便快速實踐,選擇 RabbitMQ 作爲消息隊列,MySql 作爲數據庫存儲。

對於 EFCore 及 MySql 包,安裝瞭如下幾個包

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.Tools
Pomelo.EntityFrameworkCore.MySql

注意此處 EFCore 中 MySql 版本和 CAP 中 MySql 版本兩者間依賴的 MySqlConnector 不一致會優點問題

配置服務

需要對 CAP 進行設置,比如使用的是什麼數據庫、什麼消息隊列及配置下消息隊列參數,這一系列初始化設置在 Startup.cs 中配置好。

  1. ConfigureService 中配置 DbContext 和 CAP 服務

  1. Configure 中 CAP 的引入中間件

  1. 利用 EFCore 的遷移命令生成下數據庫遷移腳本,將 DbContext 內實體生成到數據庫中

  2. 單個服務啓動後,CAP 組件會將內置表創建到數據庫中。

  1. 服務全部啓動後,RabbitMQ Client 會自動註冊到 RabbitMQ Server 中同時創建好給定的 Exchange(不給定則使用默認值),存在訂閱的服務則註冊隊列綁定到給定的 Exchange 下。

發佈事件

在 UserService 中 UserController 中注入 ICapPublisher,使用 Patch 接口更新一個 Address,然後使用 ICapPublisher 發佈一條消息。

  1. 更新本地 User 表內信息。

  2. 藉助_capPublisher 發佈事件,先將事件信息記錄到本地 MqPublish 表。

  3. 前兩步都是針對本地表操作,一個事務保證,寫入 MqPublish 成功後再由 CAP 將記錄發送到 RabbitMQ 中。

訂閱事件

在 BasketService 和 OrderService 中完成事件的訂閱。各自新建了一個 Handler 來處理消息。在 Handler 中對處理的方法加上 CapSubscribe 特性,其中監聽的是發佈事件時發送的事件名或消息名。

  1. BasketService 收到 RabbitMQ 中的消息,CAP 將消息寫入到 MqReceive 中。

  2. 調用相應的 Handler 處理事件。

  3. 更新 Basket 本地表,本地事務完成被提交。

  4. CAP 組件將本地的 MqReceive 相關記錄更改狀態到完成,如本地事務提交失敗,則再次重試。

總結

拋棄強一致性想法藉助最終一致性完成,將分佈式事務拆分成多個本地事務進行處理。採用最終一致性來使得所有本地事務完成,即使部分出現失敗,也可重試,如重試機制無效最終藉助人力完成。

在異步場景下,CAP 及其方便了我們去處理分佈式事務的過程。

當前 RabbitMQ 場景下,當某個服務做多個部署時,同一個隊列仍能保證一個消費者消費。這也避免了有些場景下,需要對資源加鎖來防止同時消費場景。

**本文 Demo:**https://gitee.com/530521314/Partner.TreasureChest/tree/master/CAPDemo?fileGuid=wwYhyjkgXDqG3TTY

轉自:微笑刺客 D

鏈接:cnblogs.com/CKExp/p/14710976.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5gqCF60Q1kChqwdv7louVQ