在前面的文章中,我們介紹過 Abp自帶的本地事件總線,但它有幾點不足
1:缺乏失敗重試機制,即若發佈事件失敗或者訂閱事件處理失敗,他沒有重試機制,導致業務和數據異常。
2:缺乏對存儲的支持,沒有集成數據庫,對事件進行存儲,事件的處理都是基於內存。
下面我們介紹CAP事件總線這個組件工具,它很好的實現了以上兩個問題,同時,也是微軟官方應用中推薦的事件總線組件。
CAP官方文檔:https://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/
因內容比較多,我們今天先介紹CAP的基本應用,基於內存存儲的模式。
我們新建一個web mvc的項目CAP事件總線,項目環境基於.NET6.0
Nuget中我們需要引用3個包:
DotNetCore.CAP:CAP的核心包
DotNetCore.CAP.InMemoryStorage:CAP的內存存儲包
Savorboard.CAP.InMemoryMessageQueue:CAP基於內存的對列包
下面2個因我們示例採用內存存儲才需要,若我們使用數據庫,則選擇安裝對應CAP的數據庫組件
然後,在Program.cs中配置CAP服務,若不是.NET6.0,則在startup.cs中注入
var services = builder.Services; services.AddCap(x => { x.UseInMemoryStorage(); x.UseInMemoryMessageQueue(); x.FailedRetryInterval = 20; //重試的間隔事件,默認是60s,官網上配置有詳細說明 });
完成以上配置,我們就可以開始使用CAP來發布和訂閱事件了
發佈消息,主要使用ICapPublisher對象
我們在Home控制器上初始化ICapPublisher 來發送一個簡單的消息
消息體包含了消息名稱和消息內容,消息名稱用於訂閱識別,如果是接入RabbitMQ,則對應它的Queue的名稱
using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; namespace CAP事件總線.Controllers { public class HomeController : Controller { private readonly ICapPublisher _capPublisher; public HomeController( ICapPublisher capPublisher) { _capPublisher = capPublisher; } public IActionResult Index() { //發送消息 _capPublisher.Publish("test.message", "我發出消息啦"); return View(); } } }
消息發送的方法支持多樣化,我們看下有哪些發送方法
/// <summary> /// 異步發佈對象消息 /// </summary> /// <param name="name">消息名稱</param> /// <param name="contentObj">消息內容體,可以是實體對象,也可以是匿名對象</param> /// <param name="callbackName">回調的消息名稱,指消息發佈後,由訂閱端處理,若訂閱端處理成功並返回內容,默認會再發佈一個消息名爲callbackName的消息,可以接着訂閱處理</param> /// <param name="cancellationToken">是否取消</param> Task PublishAsync<T>(string name, T? contentObj, string? callbackName = null, CancellationToken cancellationToken = default); /// <summary> /// 使用自定義頭異步發佈對象消息 /// </summary> /// <typeparam name="T">實體對象</typeparam> /// <param name="name">消息內容體,可以是實體對象,也可以是匿名對象</param> /// <param name="contentObj">可序列化的消息體</param> /// <param name="headers">頭信息</param> /// <param name="cancellationToken"></param> Task PublishAsync<T>(string name, T? contentObj, IDictionary<string, string?> headers, CancellationToken cancellationToken = default); /// <summary> /// Publish an object message. /// </summary> /// <param name="name">the topic name or exchange router key.</param> /// <param name="contentObj">message body content, that will be serialized. (can be null)</param> /// <param name="callbackName">callback subscriber name</param> void Publish<T>(string name, T? contentObj, string? callbackName = null); /// <summary> /// Publish an object message. /// </summary> /// <param name="name">the topic name or exchange router key.</param> /// <param name="contentObj">message body content, that will be serialized. (can be null)</param> /// <param name="headers">message additional headers.</param> void Publish<T>(string name, T? contentObj, IDictionary<string, string?> headers);
總結起來呢,就是以下幾點
1:必須要有一個消息名稱name
2:消息體只要是object類型的都可以,實體需要可序列化
3:支持消息訂閱處理完後的回調
4:支持添加消息頭
上面的示例中,我們是用最簡單的方式,發送一個消息名爲test.message的消息,內容就是一個字符串
下面我寫一個訂閱,訂閱有兩種寫法均可,一種是直接使用控制器的,另一種就是在服務類中
我們先上控制器上的代碼
using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; namespace CAP事件總線.Controllers { public class ConsumerController : Controller { [NonAction] [CapSubscribe("test.message")] public void ReceiveMessage(string message) { Console.WriteLine("message is:" + message); } } }
所有的訂閱都是採用 CapSubscribe+消息名的方式
NonAction:這個是MVC自帶的特性,跟CAP無關。若將NonAction屬性應用在Controller中的Action方法上,即使該Action方法是公共方法,也會告知ActionInvoker不要選取這個Action來執行。這個屬性主要用來保護Controller中的特定公共方法不會發布到Web上。或是當功能尚未開發完成就要進行部署時,若暫時不想將此方法刪除,也可以使用這個屬性。 簡單的說,就是通過url訪問不了,避免對外暴露。
我們運行起來看下:
從截圖中,直觀看到,消息訂閱是執行成功了。
若訂閱不是在控制器上寫呢,而是在服務層方法呢,應該怎麼訂閱?爲了驗證這個,我們新建一個MessageHandler 消息處理類
using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; namespace CAP事件總線.Models { public class MessageHandler: ICapSubscribe //消息的處理 { [CapSubscribe("test.message")] public void ReceiveMessage(string message) { Console.WriteLine("在類方法中訂閱 message is:" + message); } } }
然後我們需要注入這個類
在 programe.cs中增加
services.AddTransient<MessageHandler>();//若是服務層已經注入,則不需要
1:類方法必須繼承 ICapSubscribe 接口對象
2:類必須依賴注入到服務中
看下執行結果:
可以看到方法中的訂閱成功了。
以上是一個簡單的CAP基於內存存儲的簡單示例。
但是從運行結果來看,CAP貌似不支持 同一個消息名,多個訂閱者。
我們剛在控制器層有訂閱,現在類方法中也定義了同一個消息的訂閱,但最終它只執行了一個。這也是跟ABP有區別的地方,ABP支持多個訂閱。
CAP在同一個項目中,同一個路由只能被一個方法去執行。
當然根據羣裏問了別人,說可以用分組來實現。下一次再試試。
下一步,我們繼續驗證CAP的重試機制
我們定義一個靜態的 i 變量,初始化爲0。每次執行訂閱,都對 i+1。 別問我 每次執行爲啥 i 不是重新初始化,因爲是 靜態變量哈,自己理解。
在訂閱端,我們設置 當I<3時,都拋出異常,大於3
上代碼
using DotNetCore.CAP; using Microsoft.AspNetCore.Mvc; namespace CAP事件總線.Models { public class MessageHandler: ICapSubscribe //消息的處理 { static int i = 0; [CapSubscribe("test.message")] public void ReceiveMessage(string message) { i++; if(i<3) { throw new Exception("i 小於3,拋出異常"); } Console.WriteLine("在類方法中訂閱 message is:" + message); } } }
看運行結果
通過驗證,CAP確實有重試機制。 關於重試機制,CAP的配置項中有詳細說明
FailedRetryInterval 配置項
默認值:60 秒
在消息發送的時候,如果發送失敗,CAP將會對消息進行重試,此配置項用來配置每次重試的間隔時間。
在消息消費的過程中,如果消費失敗,CAP將會對消息進行重試消費,此配置項用來配置每次重試的間隔時間。
重試 & 間隔
在默認情況下,重試將在發送和消費消息失敗的 4分鐘後 開始,這是爲了避免設置消息狀態延遲導致可能出現的問題。
發送和消費消息的過程中失敗會立即重試 3 次,在 3 次以後將進入重試輪詢,此時 FailedRetryInterval 配置纔會生效。
示例代碼:https://gitee.com/fei686868/CAP-Test
以上就是今天分享的內容。
更多分享,請大家關注我的個人公衆號: