ABP module-zero +AdminLTE+Bootstrap Table+jQuery權限管理系統第十四節--后台工作者HangFire與ABP框架Abp.Hangfire及擴展


返回總目錄:ABP+AdminLTE+Bootstrap Table權限管理系統一期
HangFire與Quartz.NET相比主要是HangFire的內置提供集成化的控制台,方便后台查看及監控,對於大家來說,比較方便。

HangFire是什么

Hangfire是一個開源框架(.NET任務調度框架),可以幫助您創建,處理和管理您的后台作業,處理你不希望放入請求處理管道的操作:

  • 通知/通訊;
  • xml,csv,json批量導入;
  • 創建檔案;
  • 發射web hooks;
  • 刪除用戶;
  • 建立不同的圖表;
  • 圖像/視頻處理;
  • 清除臨時文件;
  • 反復出現的自動報告;
  • 數據庫維護

Hangfire支持所有類型的后台任務 - 短時間運行和長時間運行, CPU intensive I/O intensive,一次性的和經常性的。你不需要重新發明輪子 ,可以直接使用。
Hangfire包含三大核心組件:客戶端、持久化存儲、服務端。看看官方的這張圖:

image.png

Hangfire基礎

  • 基於隊列的任務處理(Fire-and-forget)
    延遲作業也只執行一次,但不會立即執行 - 只能在指定的時間間隔后執行。
var jobId = BackgroundJob.Schedule(
    () => Console.WriteLine("Delayed!"),
    TimeSpan.FromDays(7));
  • 定時執行(Recurring)
    按照指定的CRON計划, 重復執行的作業會被多次觸發。
RecurringJob.AddOrUpdate(
    () => Console.WriteLine("Recurring!"),
    Cron.Daily);
  • 延續性執行(Continuations)
    延續性任務類似於.NET中的Task,可以在第一個任務執行完之后緊接着再次執行另外的任務:
BackgroundJob.ContinueWith(
    jobId,
    () => Console.WriteLine("Continuation!"));
  • 延時執行任務(Delayed)
    延遲作業也只執行一次,但不會立即執行 - 只能在指定的時間間隔后執行。
var jobId = BackgroundJob.Schedule(
    () => Console.WriteLine("Delayed!"),
    TimeSpan.FromDays(7));
  • 批處理(Batches)
    批處理是一組自動創建的后台作業。
var batchId = Batch.StartNew(x =>
{
    x.Enqueue(() => Console.WriteLine("Job 1"));
    x.Enqueue(() => Console.WriteLine("Job 2"));
});
  • 延時批處理(Batch Continuations)
    批處理在父類完成后觸發后台作業。
Batch.ContinueWith(batchId, x =>
{
    x.Enqueue(() => Console.WriteLine("Last Job"));
});
  • 后台進程(Background Process)
    當你需要在應用程序的整個生命周期中連續運行后台進程時使用它們。
public class CleanTempDirectoryProcess : IBackgroundProcess
{
    public void Execute(BackgroundProcessContext context)
    {
        Directory.CleanUp(Directory.GetTempDirectory());
        context.Wait(TimeSpan.FromHours(1));
    }
}

后台作業是應用程序中非常重要的部分,Hangfire確保至少執行一次任務。要在應用程序重新啟動之間保留后台作業信息,所有信息都將保存在您最喜歡的持久性存儲中。
        Hangfire將您的任務保存到持久化庫匯總,並且以可靠的方式處理它們。這意味着,你可以中斷Hangfire Worder的線程,重新加載應用程序域,或者終止程序,即使這樣您的任務仍會被處理。只有在你代碼的最后一行執行完成,Hangfire才會標記這個任務完成。並且知道任務可能在最后一行代碼執行之前失敗。它包含多種 自動-重試機制,它可以自動處理在存儲或代碼執行過程中發生的錯誤。
       這對於通用托管環境(如IIS Server)非常重要。它們可以包含不的
優化,超時和錯誤處理代碼 (可能導致進程終止)來防止不好的事情發生。如果您沒有使用可靠的處理和自動機制,您的工作可能會丟失。您的最終用戶可能無限期等待某些任務,如電子郵件,報告,通知等。

實操演練

光說不練假把式,下面我們新建一個web項目,然后NuGet引入這幾個程序集
image.png

配置

然后在App_Start文件夾下Startup類配置下。
首先指定數據庫,指定Hangfire使用內存存儲后台任務信息.
Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("Default");
然后啟用HangfireServer這個中間件(它會自動釋放)
app.UseHangfireServer();
然后啟用Hangfire的儀表盤(可以看到任務的狀態,進度等信息)
app.UseHangfireDashboard();
然后配置下前台路由

  app.UseHangfireDashboard("/hangfire", new DashboardOptions
 {
         Authorization = new[] { new AbpHangfireAuthorizationFilter() }
  });

然后就是加入上面已經列出的幾個例子。

        var jobId = BackgroundJob.Schedule(
                () => Console.WriteLine("Delayed!"),
                TimeSpan.FromDays(7));


            RecurringJob.AddOrUpdate(
                        () => Console.WriteLine("Recurring!"),
                        Cron.Daily);

            BackgroundJob.ContinueWith(
                            jobId,
                            () => Console.WriteLine("Continuation!"));


            var jobId2 = BackgroundJob.Schedule(
                        () => Console.WriteLine("Delayed!"),
                        TimeSpan.FromDays(7));

效果

運行項目,輸入路徑http://<your-site>/hangfire然后就可以看到界面了。
image.png

image.png
image.png
我們分別點擊上面界面中的“加入隊列”“立即執行按鈕”,就得到下面這幅圖片。
image.png
點擊進去,可以看到如下圖。
image.png
image.png

界面看起來很清爽,而且一目了然。這就是可視化界面的好處。

Abp.Hangfire

ASP.NET Boilerplate提供后台作業和后台工作者,用於在應用程序的后台線程中執行某些任務。
后台作業用於排隊某些任務,以隊列和持續的方式在后台執行。
我們可以通過從 BackgroundJob <TArgs>類繼承或直接實現 IBackgroundJob <TArgs>接口來創建后台作業類。
這是最簡單的后台工作:

public class TestJob : BackgroundJob<int>, ITransientDependency
{
    public override void Execute(int number)
    {
        Logger.Debug(number.ToString());
    }
}

后台作業定義了一個Execute方法獲取輸入參數。參數類型被定義為泛型 類參數,如示例中所示。后台工作必須注冊到依賴注入系統中,實現ITransientDependency是最簡單的方式。下面定義一個更實際的工作,在后台隊列中發送電子郵件:

public class SimpleSendEmailJob : BackgroundJob<SimpleSendEmailJobArgs>, ITransientDependency
{
    private readonly IRepository<User, long> _userRepository;
    private readonly IEmailSender _emailSender;

    public SimpleSendEmailJob(IRepository<User, long> userRepository, IEmailSender emailSender)
    {
        _userRepository = userRepository;
        _emailSender = emailSender;
    }

    public override void Execute(SimpleSendEmailJobArgs args)
    {
        var senderUser = _userRepository.Get(args.SenderUserId);
        var targetUser = _userRepository.Get(args.TargetUserId);

        _emailSender.Send(senderUser.EmailAddress, targetUser.EmailAddress, args.Subject, args.Body);
    }
}

我們注入了用戶倉儲(為了獲得用戶信息)和email發送者(發送郵件的服務),然后簡單地發送了該郵件。SimpleSendEmailJobArgs是該工作的參數,它定義如下:

[Serializable]
public class SimpleSendEmailJobArgs
{
    public long SenderUserId { get; set; }

    public long TargetUserId { get; set; }

    public string Subject { get; set; }

    public string Body { get; set; }
}

作業參數應該是可序列化的,因為它 被序列化並存儲在數據庫中。雖然ASP.NET Boilerplate默認后台作業管理器使用JSON 序列化(不需要[Serializable]屬性),但最好定義[Serializable]屬性,因為將來可能會切換到另一個作業管理器,在二進制序列化。保持你的參數簡單(如 DTO),不要包含 實體或其他不可序列化的對象。如SimpleSendEmailJob示例所示,我們只能存儲 一個實體的Id,並從作業中的存儲庫獲取該實體。將新作業添加到隊列中定義后台作業后,我們可以注入並使用IBackgroundJobManager 將作業添加到隊列中。查看上面定義的TestJob示例:

public class MyService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public MyService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public void Test()
    {
        _backgroundJobManager.Enqueue<TestJob, int>(42);
    }
}

當入隊(Enqueue)時,我們將42作為參數傳遞。IBackgroundJobManager將會實例化並使用42作為參數執行TestJob
讓我們看一下如何為上面定義的SimpleSendEmailJob添加一個新的工作:

[AbpAuthorize]
public class MyEmailAppService : ApplicationService, IMyEmailAppService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public MyEmailAppService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task SendEmail(SendEmailInput input)
    {
            await _backgroundJobManager.EnqueueAsync<SimpleSendEmailJob, SimpleSendEmailJobArgs>(
            new SimpleSendEmailJobArgs
            {
                Subject = input.Subject,
                Body = input.Body,
                SenderUserId = AbpSession.GetUserId(),
                TargetUserId = input.TargetUserId
            });
    }
}

Enqueu(或EnqueueAsync)方法具有其他參數,如優先級 和延遲。

默認后台作業管理器

IBackgroundJobManagerBackgroundJobManager 默認實現。它可以被另一個后台作業提供者替代(參見 hangfire集成)。有關默認BackgroundJobManager的一些信息:
這是一個簡單的作業隊列在 單線程中作為FIFO使用。它使用IBackgroundJobStore來堅持作業。

  • 它重試作業執行,直到作業 成功運行(不會拋出任何異常,但記錄它們)或 超時。作業的默認超時時間為2天。
  • 它成功執行時從商店(數據庫)中刪除作業。如果超時,則將其設置為廢棄並保留在數據庫中。
  • 它越來越多地等待重新工作。等待1分鍾第一次重試,2分鍾第二次重試,4分鍾第三次重試等等。
  • 它以固定的時間間隔輪詢商店的工作。按優先級(asc)查詢作業,然后按try count(asc)進行排序。

后台工作存儲

默認的BackgroundJobManager需要一個數據存儲來保存和獲取作業。如果您沒有實現IBackgroundJobStore,那么它使用 InMemoryBackgroundJobStore,它不會將作業保存在持久數據庫中。您可以簡單地實現它來將作業存儲在數據庫中,或者可以使用 已經實現它的module-zero
如果您使用第三方工作經理(如 Hanfgire),則無需實施IBackgroundJobStore

配置

您可以在 模塊的PreInitialize方法中使用Configuration.BackgroundJobs來配置后台作業系統。
禁用作業執行
您可能需要為應用程序禁用后台作業執行:

public class MyProjectWebModule : AbpModule
{
    public override void PreInitialize()
    {
        Configuration.BackgroundJobs.IsJobExecutionEnabled = false;
    }

    //...
}

這種情況很少見。但是,認為您正在同一個數據庫上運行應用程序的多個實例(在Web場中)。在這種情況下,每個應用程序將查詢作業的相同數據庫並執行它們。這導致同一個工作的多個執行和一些其他問題。為了防止它,你有兩個選擇:

  • 您只能為應用程序的一個實例啟用作業執行。
  • 您可以禁用所有Web應用程序實例的作業執行,並創建一個執行后台作業的獨立應用程序(例如:Windows服務)。

異常處理

由於默認的后台作業管理器應該重新嘗試失敗的作業,它會處理(並記錄)所有異常。如果你想在發生異常時得到通知,你可以創建一個事件處理程序來處理 AbpHandledExceptionData。后台管理器用一個包裝了真正異常的BackgroundJobException異常對象觸發這個事件(對於實際的異常,得到InnerException)。

Hangfire集成

  • 后台作業管理器被設計為可被另一個后台作業管理器替換。請參閱 Hangfire集成文檔以用Hangfire替換它。
  • 后台工作者與后台工作不同。它們是在后台運行的應用程序中的簡單 獨立線程。通常,他們定期執行一些任務。例子;
  • 后台工作人員可以定期運行以 刪除舊日志。
  • 后台工作人員可以定期來 判斷非活躍用戶和發送電子郵件要返回給應用程序。

創建一個后台工作者

要創建一個后台工作者,我們應該實現 IBackgroundWorker接口。或者,我們可以根據我們的需要從BackgroundWorkerBase PeriodicBackgroundWorkerBase繼承 。
假設我們想在最近30天內沒有登錄到應用程序,使用戶狀態passive。看代碼:

public class MakeInactiveUsersPassiveWorker : PeriodicBackgroundWorkerBase, ISingletonDependency
{
    private readonly IRepository<User, long> _userRepository;

    public MakeInactiveUsersPassiveWorker(AbpTimer timer, IRepository<User, long> userRepository)
        : base(timer)
    {
        _userRepository = userRepository;
        Timer.Period = 5000; //5 seconds (good for tests, but normally will be more)
    }

    [UnitOfWork]
    protected override void DoWork()
    {
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            var oneMonthAgo = Clock.Now.Subtract(TimeSpan.FromDays(30));

            var inactiveUsers = _userRepository.GetAllList(u =>
                u.IsActive &&
                ((u.LastLoginTime < oneMonthAgo && u.LastLoginTime != null) || (u.CreationTime < oneMonthAgo && u.LastLoginTime == null))
                );

            foreach (var inactiveUser in inactiveUsers)
            {
                inactiveUser.IsActive = false;
                Logger.Info(inactiveUser + " made passive since he/she did not login in last 30 days.");
            }

            CurrentUnitOfWork.SaveChanges();
        }
    }
}

這是現實的代碼,可以直接在module-zero的 ASP.NET Boilerplate中運行 。

  • 如果您從PeriodicBackgroundWorkerBase派生(如本示例中所示),則應該實施DoWork方法來執行您的定期工作代碼。
  • 如果從BackgroundWorkerBase派生或直接實現IBackgroundWorker,則將覆蓋/實現StartStopWaitToStop方法。StartStop方法應該是非阻塞的,WaitToStop方法應該等待 worker完成當前的關鍵任務。

注冊后台工作者

創建后台工作者后,我們應該將其添加到 IBackgroundWorkerManager。最常見的地方是你的模塊的PostInitialize方法:

public class MyProjectWebModule : AbpModule
{
    //...

    public override void PostInitialize()
    {
        var workManager = IocManager.Resolve<IBackgroundWorkerManager>();
        workManager.Add(IocManager.Resolve<MakeInactiveUsersPassiveWorker>());
    }
}

雖然我們通常在PostInitialize中加入工作人員,但對此沒有限制。您可以在任何地方注入IBackgroundWorkerManager,並在運行時添加工作人員。當您的應用程序正在關閉時,IBackgroundWorkerManager將停止並釋放所有注冊的工作人員。

后台工作者生命周期

后台工作人員通常以單例的。但是沒有限制。如果您需要同一工人類的多個實例,則可以將其設置為暫時的,並向IBackgroundWorkerManager添加多個實例。在這種情況下,您的工作人員可能是參數化的(例如,您有一個LogCleaner類,但是他們監視的兩個LogCleaner工作者實例並清除不同的日志文件夾)。

高級調度

ASP.NET Boilerplate的后台工作系統很簡單。除了定期運行的工人之外,它沒有一個時間表系統。如果您需要更高級的計划功能,我們建議您檢查Quartz或其他庫。

讓您的應用程序一直運行

后台作業和工作人員只有在您的應用程序正在運行時才有效 如果很長一段時間沒有對Web應用程序執行任何請求,ASP.NET應用程序將默認關閉。因此,如果您在Web應用程序中托管后台作業(這是默認行為),則應確保您的Web應用程序配置為始終運行。否則,后台作業只在您的應用程序正在使用時才起作用。
有一些技術來完成這一點。最簡單的方法是定期從外部應用程序請求您的Web應用程序。因此,您也可以檢查您的Web應用程序是否已啟動並正在運行。 Hangfire文檔解釋了其他一些方法。
通過以上官方文檔,我們在程序里配置一下。
image.png

運行一下效果是一樣的。
image.png
image.png

Hangfire優點

Hangfire是一個后台可監控的應用,不用每次都要從服務器拉取日志查看,在沒有ELK的時候相當不方便。Hangfire控制面板不僅提供監控,也可以手動的觸發執行定時任務。如果在定時任務處理方面沒有很高的要求,比如一定要5s定時執行,Hangfire值得擁有。拋開這些,Hangfire優勢太明顯了:

  • 持久化保存任務、隊列、統計信息
  • 重試機制
  • 多語言支持
  • 支持任務取消
  • 支持按指定Job Queue處理任務
  • 服務器端工作線程可控,即job執行並發數控制
  • 分布式部署,支持高可用
  • 良好的擴展性,如支持IOCHangfire Dashboard授權控制、Asp.net Core、持久化存儲等

Hangfire擴展

Hangfire擴展性大家可以參考這里,有幾個擴展是很實用的.下面這些關於Hangfire擴展大家可以自己查資料。后面如果有機會的話,我再補上。

  • Hangfire Dashborad日志查看
  • Hangfire Dashborad授權
  • IOC容器之Autofac
  • RecurringJob擴展
  • 與MSMQ集成
  • 持久化存儲之Redis

福利及其他

其實Hangfire還是蠻簡單的。如果你需要了解更多關於Abp.Hangfire的內容,建議你去看一下github上一個專門關於Abp.Hangfire的demo,
地址:https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/BackgroundJobAndNotificationsDemo
另外 ABP后台工作者類使用HANGFIRE這篇文章
講解abp Hangfire 缺點是工作者類依賴了具體的基類(PeriodicBackgroundWorkerBase),就會存在應用程序耦合。以及解決耦合的辦法,算是對abp Hangfire的擴展,我不太認同,各有看法吧。

就以上面我說到的項目 abp Hangfire demo 項目 BackgroundJobAndNotificationsDemo為例,首先是一個創建郵件發送的任務。
image.png
image.png
,創建到數據庫之后,HangFire會自動在數據庫創建幾張表。
image.png
然后配置我上面說到的幾個配置步驟之后。運行項目可以看到。每隔5秒鍾會請求一下后台任務。
image.png
界面上就有了相應的效果

image.png
大家可以自行下載Demo下來看一下相關的寫法以及配置。

本文githubd地址:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS.git
后台工作者HangFire與ABP框架Abp.Hangfire及擴展
返回總目錄:ABP+AdminLTE+Bootstrap Table權限管理系統一期


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM