前言:
本文簡單介紹DotNetCore.CAP的簡單使用,關於分佈式事務中的“CAP原則”和“BASE理論”以及分佈式事務的其他解決方案不做過多的介紹
CAP介紹
CAP是一個在分佈式系統(SOA)/微服務系統(MicroService)中實現事件總線及最終一致性(分佈式事務)的一個開源的C#庫,具有輕量級,高性能,易使用等特點
CAP 具有Event Bus的所有功能,簡化EventBus中de發佈/訂閱
CAP 具有消息持久化的功能,服務進行重啓或者宕機不比擔心消息丟失保證可靠性
CAP安裝配置
NuGet安裝:DotNetCore.CAP(基礎支持),DotNetCore.CAP.RabbitMQ(底層消息隊列),DotNetCore.CAP.SqlServer(數據庫)
底層消息隊列支持:支持使用 RabbitMQ,Kafka,Azure Service Bus等
數據庫支持: Sql Server,MySql,PostgreSql,MongoDB
Startup:
public void ConfigureServices(IServiceCollection services) { services.AddCap(x => { var OrderDatabaseString = Configuration.GetConnectionString("OrderDatabaseString"); var rabbitMQ = Configuration.GetConnectionString("rabbitMQ"); x.UseSqlServer(OrderDatabaseString); x.UseRabbitMQ(rabbitMQ); x.FailedRetryCount = 10;//重試最高次數 x.FailedRetryInterval = 10; //重試間隔 x.FailedThresholdCallback = Failed => { /*重試次數達到上限*/ }; }); }
當項目正確配置完成啓動後,系統會自動創建[cap].[Published] 和[cap].[Received]本地消息業務表
CAP的簡單使用
用一個簡單的項目舉例,一個訂單服務,一個產品服務(產品服務爲集羣);用戶下單調用訂單服務下單接口,下單接口需要調用產品服務的減庫存接口
1,創建訂單發送消息
public async Task<string> CreateOrder() { var orderId = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(0, 100000000); var order = new Orders() { orderId = orderId, skunanme = "測試商品", skuId = 40004456666244, num = 50 }; var headers = new Dictionary<string, string>(); headers.Add("Buy", "111"); /*消息接收方名稱*/ this.PublishName = Enum.ProductCheckHouseNum.ToString(); /*推送內容*/ this.PublishContent = order; /*推送Header*/ this.PublishHeader = headers; using (var connection = Db.OpenConnection(1)) { IDbTransaction transaction = null; try { transaction = connection.BeginTransaction(_capBus, false); var result = await connection.InsertAsync(order, transaction) > 0; await _capBus.PublishAsync(this.PublishName, this.PublishContent); if (result) transaction.Commit(); else transaction.Rollback(); orderId = result ? orderId : ""; } catch (Exception ex) { transaction?.Rollback(); } } return orderId; }
2,消息接收
/// <summary> /// 接收下單庫存驗證消息 /// </summary> [NonAction] [CapSubscribe("ProductCheckHouseNum")] public void ProductCheckHouseNum(DB.Model.Orders Order, [FromCap] CapHeader header) { if (Order != null) { } }
注意接收方的CapSubscribe("ProductCheckHouseNum")和推送方設置的名稱必須一直,
3,補償事務
某些情況下消費者需要返回值以告訴發佈者執行結果,以便於發佈者實施一些動作,通常情況下這屬於補償範圍,以上面的項目爲例,商品在接收到消息驗證之後向訂單服務返回執行結果
現在修改一下訂單服務的推送
public async Task<string> CreateOrder() { var orderId = DateTime.Now.ToString("yyyyMMdd") + new Random().Next(0, 100000000); var order = new Orders() { orderId = orderId, skunanme = "測試商品", skuId = 40004456666244, num = 50 }; var headers = new Dictionary<string, string>(); headers.Add("Buy", "111"); /*消息接收方名稱*/ this.PublishName = Enum.ProductCheckHouseNum.ToString(); /*推送內容*/ this.PublishContent = order; /*推送Header*/ this.PublishHeader = headers; using (var connection = Db.OpenConnection(1)) { IDbTransaction transaction = null; try { transaction = connection.BeginTransaction(_capBus, false); var result = await connection.InsertAsync(order, transaction) > 0; await _capBus.PublishAsync(this.PublishName, this.PublishContent, "callbackFunction"); if (result) transaction.Commit(); else transaction.Rollback(); orderId = result ? orderId : ""; } catch (Exception ex) { transaction?.Rollback(); } } return orderId; } /// <summary> /// 回調函數 /// </summary> /// <param name="order"></param> [NonAction] [CapSubscribe("callbackFunction")] public void MarkOrderStatus(DB.Model.Orders order) { if (order != null) { } }
對應的修改商品服務:
/// <summary> /// 接收下單庫存驗證消息 /// </summary> [NonAction] [CapSubscribe("ProductCheckHouseNum")] public async Task<Orders> ProductCheckHouseNums(DB.Model.Orders Order, [FromCap] CapHeader header) { if (Order != null) { //業務處理 } return Order; }
在CAP推送方法PublishAsync的調用時指定callbackName
來得到消費者的執行結果,注意下游服務一定要return
4,冪等性驗證
冪等性指的是多次操作,結果是一致的 ,但是CAP無法保證消息不重複(即使是能保證該有的驗證還是要自己弄),實際使用中需要自己考慮一下消息的重複過濾和冪等性
在上面的例子中接收方是模擬的集羣,相同的消息會接收兩次,相應的推送方訂單服務也會接收到兩次
所以爲保證冪等性在商品庫中建立了一個流水錶,用商品Id和訂單號作爲聯合主鍵保證業務處理的唯一,訂單服務同理,, 當然這只是一種處理方式,也可以使用Redis等進行處理
總結
這只是CAP的簡單使用,更多介紹請訪問:https://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/