默认的后台作业管理器是通过一个后台工作者来执行后台任务的,这个实现叫做 BackgroundJobWorker,这个后台工作者的生命周期也是单例的。后台作业的具体执行逻辑里面,涉及到了以下几个类型的交互。
类型 作用AbpBackgroundJobOptions 提供每个后台任务的配置信息,包括任务的类型、参数类型、任务名称数据。
AbpBackgroundJobWorkerOptions 提供后台作业工作者的配置信息,例如每个周期 最大执行的作业数量、后台
工作者的 执行周期、作业执行 超时时间 等。
BackgroundJobConfiguration 后台任务的配置信息,作用是将持久化存储的作业信息与运行时类型进行绑定
和实例化,以便 ABP vNext 来执行具体的任务。
IBackgroundJobExecuter 后台作业的执行器,当我们从持久化存储获取到后台作业信息时,将会通过
这个执行器来执行具体的后台作业。
IBackgroundJobSerializer 后台作业序列化器,用于后台作业持久化时进行序列化的工具,默认采用的
是 JSON.NET 进行实现。
JobExecutionContext 执行器在执行后台作业时,是通过这个上下文参数进行执行的,在这个上下
文内部,包含了后台作业的具体类型、后台作业的参数值。
IBackgroundJobStore 前面已经讲过了,这个是用于后台作业的持久化存储,默认实现是存储在内存。
BackgroundJobPriority 后台作业的执行优先级定义,ABP vNext 在执行后台任务时,会根据任务的优
先级进行排序,以便在后面执行的时候优先级高的任务先执行。
我们来按照逻辑顺序走一遍它的实现,首先后台作业的执行工作者会从持久化存储内,获取 MaxJobFetchCount 个任务用于执行。从持久化存储获取后台作业信息(BackgroundJobInfo),是由 IBackgroundJobStore 提供的。
var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>(); var waitingJobs = store.GetWaitingJobs(WorkerOptions.MaxJobFetchCount); // 不存在任何后台作业,则直接结束本次调用。 if (!waitingJobs.Any()) { return; }InMemoryBackgroundJobStore 的相关实现:
public List<BackgroundJobInfo> GetWaitingJobs(int maxResultCount) { return _jobs.Values .Where(t => !t.IsAbandoned && t.NextTryTime <= Clock.Now) .OrderByDescending(t => t.Priority) .ThenBy(t => t.TryCount) .ThenBy(t => t.NextTryTime) .Take(maxResultCount) .ToList(); }上面的代码可以看出来,首先取得放弃的任务 且达到执行时间的任务,然后根据任务的优先级从高到低进行排序。重试次数少的优先执行,预计执行时间越早的越先执行。最后从这些数据中,筛选出 maxResultCount 结果并返回。
说到这里,我们来看一下这个 NextTryTime 是如何被计算出来的?回想起最开始的后台作业管理器,我们在添加一个后台任务的时候,就会设置这个后台任务的 预计执行时间。第一个任务被添加到执行队列中时,它的值一般是 Clock.Now ,也就是它被添加到队列的时间。
不过 ABP vNext 为了让那些经常执行失败的任务,有比较低的优先级再执行,就在每次任务执行失败之后,会将 NextTryTime 的值指数级进行增加。这块代码可以在 CalculateNextTryTime 里面看到,也就是说某个任务的执行 失败次数越高,那么它下一次的预期执行时间就会越远。
protected virtual DateTime? CalculateNextTryTime(BackgroundJobInfo jobInfo, IClock clock) { // 一般来说,这个 DefaultWaitFactor 因子的值是 2.0 。 var nextWaitDuration = WorkerOptions.DefaultFirstWaitDuration * (Math.Pow(WorkerOptions.DefaultWaitFactor, jobInfo.TryCount - 1)); // 同执行失败的次数进行挂钩。 var nextTryDate = jobInfo.LastTryTime?.AddSeconds(nextWaitDuration) ?? clock.Now.AddSeconds(nextWaitDuration); if (nextTryDate.Subtract(jobInfo.CreationTime).TotalSeconds > WorkerOptions.DefaultTimeout) { return null; } return nextTryDate; }当预期的执行时间都超过 DefaultTimeout 的超时时间时(默认为 2 天),说明这个任务确实没救了,就不要再执行了。