CAP事件總線在NetCore中的應用

在前面的文章中,我們介紹過 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

以上就是今天分享的內容。

 

更多分享,請大家關注我的個人公衆號:

 

 

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