1. 程式人生 > >後臺工作者HangFire與ABP框架Abp.Hangfire及擴充套件

後臺工作者HangFire與ABP框架Abp.Hangfire及擴充套件

HangFire與Quartz.NET相比主要是HangFire的內建提供整合化的控制檯,方便後臺檢視及監控,對於大家來說,比較方便。

HangFire是什麼

Hangfire是一個開源框架(.NET任務排程框架),可以幫助您建立,處理和管理您的後臺作業,處理你不希望放入請求處理管道的操作:

  • 通知/通訊;
  • xml,csv,json批量匯入;
  • 建立檔案;
  • 發射web hooks;
  • 刪除使用者;
  • 建立不同的圖表;
  • 影象/視訊處理;
  • 清除臨時檔案;
  • 反覆出現的自動報告;
  • 資料庫維護

Hangfire支援所有型別的後臺任務 - 短時間執行和長時間執行, CPU intensiveI/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介面。或者,我們可以根據我們的需要從BackgroundWorkerBasePeriodicBackgroundWorkerBase繼承 。
假設我們想在最近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

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