ABP官方文档翻译 7.1 后台Jobs和Workers

时间:2021-10-03 21:54:41

后台Jobs和Workers

介绍

  ABP提供了后台jobs和workers,他们用于在应用的后台线程中执行一些任务。

后台Jobs

  后台Jobs用来排队一些需要在后台队列或持久化的方式执行一些任务。以下几种情况可以使用后台jobs:

  • 执行一些长时运行的任务而不需要用户等待。例如,用户按了一个'report'按钮开始一个长时运行的报表job。可以将这个job添加到队列,当执行完成的时候通过邮件发送给用户报表结果。
  • 创建一个重试且持久化的任务来保证一段代码的成功运行。例如:可以在后台job中发送邮件来克服临时错误并保证它最终被发送。这样,当发送邮件时,用户不必等待。

关于Job持久化

  参见后台Job存储了解更多关于job持久化的信息。

创建后台Job

  我们可以通过从BackgroundJob<TArgs>类或直接实现IBackgroundJob<TArgs>接口来创建一个后台job类。

  这是最简单的后台job:

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

  后台job定义了Excute方法,它有一个输入参数。参数类型为泛型类参数。

  后台job必须注册到依赖注入系统。实现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);
}
}

  我们注入了user仓储(获得用户电子邮件)和电子邮件sender(发送邮件的服务)简单的发送邮件。SimpleSendEmailJobArgs为job的参数,定义如下:

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

public long TargetUserId { get; set; }

public string Subject { get; set; }

public string Body { get; set; }
}

  job参数应该是可以序列化的,因为它被序列化并存储在数据库中。ABP默认后台job管理器使用JSON序列化(它不需要[Serilizable]特性),但是最好定义[Serializable]特性,因为我们可能在将来切换到其他job管理器,它可能使用.NET的内置二进制序列化。

  保持参数简单(如DTOs),不要包含实体或其他非序列化的对象。如在SimpleSendEmailJob示例中所示,我们可以存储实体的Id并从实体的仓储中获得实体。

在队列中添加一个新Job

  定义后台job之后,我们可以注入并使用IBackgroundJobManager来添加job到队列。参见TestJob定义示例:

public class MyService
{
private readonly IBackgroundJobManager _backgroundJobManager;

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

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

  当入队时,我们使用42作为参数。IBackgroundJobManager将实例化并执行TestJob,参数为42。

  让我们添加一个上面定义的新的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
});

}
}

  Enque(或EnqueueAsync)方法还有其他参数如prioritydelay

默认的后台Job管理器

  IBackgroundJobManager默认由BackgroundJobManager实现。它可以被其他后台job提供者(参见hangfire集成)取代。BackgroundJobManager的一些参考信息:

  • 它是一个单线程的FIFO的简单job队列。它使用IBackgroundJobStore来持久化jobs(参见下部分)。
  • 在job成功运行(不抛出任何异常但是会记录)或超时前会一直重试。默认的重试时间为2天。
  • 当成功执行后,它从仓储(数据库)中删除job。如果它超时了,就设置为abandoned并保留在数据库中。
  • 每次重试前它会递增时间。第一次尝试等待1分钟,第二次等待2分钟,第三次等待4分钟...
  • 它以固定间隔轮训jobs仓储。按优先级(升序)、重试次数(升序)排列查询jobs。

后台Job存储

  默认的BackgroundJobManager需要一个数据仓储来保存和获取jobs。如果你没实现IBackgroundJobStore那么它使用InMemoryBackgroundJobStore,它不在持久化的数据库中存储jobs。你可以简单的实现它来在数据库中存储jobs或者可以使用已经实现了它的module-zero

  如果你使用了第三方的job管理器(如Hangfire),就不需要实现IBackgroundJobStore了。

配置

  你可以在模块的PreInitialize方法中使用Configuration.BackgroundJobs来配置后台job系统。

禁用Job执行

  你可以禁用应用程序的后台job执行:

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

//...
}

  几乎不需要这样做。但是,假如你的应用运行了多个实例并使用同样的数据库(在web farm中)。在这种情况下,每个应用将使用同样的数据库查询job并执行。这会导致同样job的多次执行及其他的问题。为了阻止这种情况,有两种选择:

  • 你可以只为应用程序的一个实例启用job执行。
  • 你可以为应用程序的所有实例禁用job执行,然后创建一个单独的执行后台jobs的应用程序(例如:windows service)。

异常处理

  因为默认的后台管理器会自动重试失败的jobs,它处理(并记录)所有的异常。如果当异常发生时你想要得到通知,可以创建一个事件处理者来处理AbpHandledExceptionData。后台管理器触发这个事件,它有一个BackgroundJobException异常对象参数,这个参数包装了真正的异常(获取InnerException获取真正的异常)。

Hangfire集成

  后台管理器设计为可以被其他后台job管理器代替。参见hangfire集成文档,使用Hangfire替换它。

后台Workers

  后台Workers与后台Jobs不同。他们为在后台运行的简单独立的线程。通常,他们定期执行一些任务。例如:

  • 后台worker可以定期的删除老的日志。
  • 后台worker可以定期的判定不活动的用户并发送邮件来通知用户。

创建后台Workers

  创建一个后台worker,我们需要实现IBackgroundWorker接口。作为另一个选择,我们可以基于我们的需求继承BackgroundWorkerBasePeriodicBackgroundWorkerBase

  假如我们想使用户失活,如果他30天内没有登录应用的话。参见下面的代码:

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();
}
}
}

  这是一段直接在ABP moudle-zero中使用的真实代码。

  • 如果你从PeriodicBackgroundWokerBase(如在本例中)继承,需要实现DoWork方法来执行你的定期执行代码。
  • 如果你从BackgroundWorkerBase继承或直接实现IBackgroundWorker接口,你需要重写/实现Start,StopWaitToStop方法。Start和Stop方法需要为非阻塞的,WaitToStop方法需要等待worker完成它当前关键的job。

注册后台Workers

  创建后台worker后,我们需要把它添加到IBackgroundWorkerManger。最常见的地方为模块的PostInitialize方法:

public class MyProjectWebModule : AbpModule
{
//...

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

  我们通常在PostInitialize方法中添加worker,但并不限制。你可以在任何地方注入IBackgroundWorkerManager并添加worker。当应用程序关闭时,IBackgroundWorkerManager将停止并释放所有注册的worker。

后台Worker生命周期

  后台workder一般实现为单例模式。但是并没有限制。如果你需要同个worker类的多个实例,你可以使它为瞬时的并添加多个实例到IBackgroundWorkerManager。在这种情况下,你的worker将为参数化的(也就是说你有一个单独的LogCleaner类但是两个LogCleaner worker实例他们监视并清理不同的日志文件夹)。

高级计划安排

  ABP后台worker系统比较简单。除了上面展示的定期执行worker,它没有计划安排系统。如果你需要更多计划安排的特征,我们建议你使用Quartz或其他类库。

让你的应用一直运行

  后台jobs和workers只有当你应用程序运行时才会工作。如果长时间web应用没有请求执行,ABP应用默认会关闭。所以,如果你在web应用中(这是默认行为)寄宿后台job的执行,你需要保证你的web应用配置为一直执行。否则,后台job只有应用在使用时才能工作。

  有一些技术可以实现。最简单的方式是使用外部应用定期请求你的web应用。这样,你可以检查文本应用是否已启动并运行。Hangfire文档解释了一些其他的方式。

 

返回主目录