后台工做者HangFire与ABP框架Abp.Hangfire及扩展

同步博客园地址:www.cnblogs.com/anyushengcm…
同步简书地址:www.jianshu.com/p/ebe390e48…html

HangFire与Quartz.NET相比主要是HangFire的内置提供集成化的控制台,方便后台查看及监控,对于你们来讲,比较方便。git

HangFire是什么

Hangfire是一个开源框架(.NET任务调度框架),能够帮助您建立,处理和管理您的后台做业,处理你不但愿放入请求处理管道的操做:github

  • 通知/通信;
  • xml,csv,json批量导入;
  • 建立档案;
  • 发射web hooks;
  • 删除用户;
  • 创建不一样的图表;
  • 图像/视频处理;
  • 清除临时文件;
  • 反复出现的自动报告;
  • 数据库维护

Hangfire支持全部类型的后台任务 - 短期运行和长时间运行, CPU intensiveI/O intensive,一次性的和常常性的。你不须要从新发明轮子 ,能够直接使用。
Hangfire包含三大核心组件:客户端、持久化存储、服务端。看看官方的这张图:web

image.png
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
image.png

配置

而后在App_Start文件夹下Startup类配置下。
首先指定数据库,指定Hangfire使用内存存储后台任务信息.
Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("Default");
而后启用HangfireServer这个中间件(它会自动释放)
app.UseHangfireServer();
而后启用Hangfire的仪表盘(能够看到任务的状态,进度等信息)
app.UseHangfireDashboard();
而后配置下前台路由json

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

而后就是加入上面已经列出的几个例子。bash

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

咱们分别点击上面界面中的“加入队列”“当即执行按钮”,就获得下面这幅图片。
image.png
image.png

点击进去,能够看到以下图。
image.png
image.png

image.png
image.png

界面看起来很清爽,并且一目了然。这就是可视化界面的好处。并发

Abp.Hangfire

ASP.NET Boilerplate提供后台做业和后台工做者,用于在应用程序的后台线程中执行某些任务。
后台做业用于排队某些任务,以队列和持续的方式在后台执行。
咱们能够经过从BackgroundJob <TArgs>类继承或直接实现IBackgroundJob <TArgs>接口来建立后台做业类。
这是最简单的后台工做:app

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
image.png

image.png
image.png

其余

其实Hangfire仍是蛮简单的。若是你须要了解更多关于Abp.Hangfire的内容,建议你去看一下github上一个专门关于Abp.Hangfire的demo,

地址:github.com/aspnetboile…
另外 ABP后台工做者类使用HANGFIRE这篇文章

讲解abp Hangfire 缺点是工做者类依赖了具体的基类(PeriodicBackgroundWorkerBase),就会存在应用程序耦合。以及解决耦合的办法,算是对abp Hangfire的扩展,我不太认同,各有见解吧。

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

本文githubd地址:github.com/Jimmey-Jian…

相关文章
相关标签/搜索