Hangfire后台任务是Net项目中用得比较多的一种任务调度器。这里结合我在实际工作中遇到的一些问题以及解决的方案给大家进行一下分享,免得走弯弯路。话不多说直接开整。
Hangfire官方文档:Hangfire
最近封装了一个参数化Job,循环周期性Job特性化,以及Job执行结果通知处理,可以通过NuGet搜索OpenDeepSpace.NetCore.Hangfire使用。
项目Github地址:OpenDeepSpace.NetCore.Hangfire
Hangfire关于任务重试次数与重试时间间隔设置
Hangfire全局重试次数与时间间隔设置
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute {Attempts = int.MaxValue, DelaysInSeconds = new[] {1}});
基于特性设置单个Job的时间间隔
[AutomaticRetry(Attempts = 3, DelaysInSeconds =new []{100,200,300})]
Hangfire任务执行状态跟踪拦截
/// <summary>
/// Job状态过滤器
/// </summary>
public class JobStateFilter : JobFilterAttribute, IElectStateFilter, IServerFilter, IApplyStateFilter
{
/// <summary>
/// 执行完成
/// </summary>
/// <param name="filterContext"></param>
public void OnPerformed(PerformedContext filterContext)
{
var retryCount=filterContext.GetJobParameter<int>("RetryCount");//获取重试次数
// you have an option to move all code here on OnPerforming if you want.
int i = 0;
}
/// <summary>
/// 执行中
/// </summary>
/// <param name="filterContext"></param>
public void OnPerforming(PerformingContext filterContext)
{
// do nothing
int i = 0;
}
/// <summary>
/// 状态转换
/// </summary>
/// <param name="context"></param>
public void OnStateElection(ElectStateContext context)
{
// all failed job after retry attempts comes here
var failedState = context.CandidateState as FailedState;
if (failedState == null) return;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <param name="transaction"></param>
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
int i = 0;
}
/// <summary>
/// 状态应用
/// </summary>
/// <param name="context"></param>
/// <param name="transaction"></param>
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
//一次Job执行完成最终状态在这里
//达到最大重试次数之后 如果状态为失败 表示任务执行失败
var failedState = context.NewState as FailedState;
if (failedState != null)
{
int i = 0;
//hangfire重试失败 通过邮件推送
}
}
}
//第一种方式全局添加
GlobalJobFilters.Filters.Add(new JobStateFilter());
//第二种方式添加
var options = new BackgroundJobServerOptions
{
FilterProvider = new JobFilterCollection { new JobStateFilter() };
};
app.UseHangfireServer(options, storage);
JobStateFilter中一个Job在里面执行方法的过程如下:
Job开始调度----->执行OnStateElection----->执行OnStateUnapplied----->执行OnStateApplied---->(如果状态不为Processing又会去执行OnStateElection依次顺序执行, 当状态变为Processing时执行OnPerforming---->任务实际执行完成----->执行OnPerformed---->执行OnStateElection---->执行OnStateUnapplied—>执行OnStateApplied)----->结束
Job过期时间设置(一般指成功执行了的Job即状态为Succeeded的Job在数据库中持久化时间Hangfire默认为1天)
Job持久化到数据库后,对于成功的Job有一个过期时间设置,到了过期时间之后的某个时刻就会从数据库中删除。
如上图Hangfire自带的过期时间为1天,即你可以理解为成功的Job只保留当天和一天前的。这是Hangfire为了性能考虑。
这里可能你的项目中需要把这个成功的Job驻留时间延长一些(主要考虑到Hangfire中Job执行成功,但是由于其他问题,需要重启Job的时候,如果保留时间过短可能会找不到这个成功的Job)。这里可以通过以下代码实现设置成功Job的过期时间。这个过期时间设置长短根据自己需要,建议不要设置太长。
/// <summary>
/// 成功的Job过期处理 实现IStateHandler处理接口 可以对不同状态的HangfireJob进行自己的处理
/// </summary>
public class SucceededJobExpiredHandler : IStateHandler
{
public string StateName => SucceededState.StateName;
/// <summary>
/// Job过期时间
/// </summary>
private TimeSpan JobExpirationTimeout;
/// <summary>
///
/// </summary>
/// <param name="JobExpirationTimeoutMins">过期分钟数 默认跟随Hangfire的默认过期时间1天 即成功的数据只保留1天前的</param>
public SucceededJobExpiredHandler(int JobExpirationTimeoutMins=24*60)
{
JobExpirationTimeout = TimeSpan.FromMinutes(JobExpirationTimeoutMins);
}
public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
context.JobExpirationTimeout = JobExpirationTimeout;
}
public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
}
}
//HangfireJob成功的保留时间设置 即在数据库中成功的Job驻留时间 默认为1天
GlobalStateHandlers.Handlers.Add(new SucceededJobExpiredHandler(7 * 24 * 60));//成功的Job过期时间为1周
你可以根据自己的需要在某一个需要对任务进行调整的环节自己加入代码逻辑块来调整,我这里主要用在任务达到执行次数失败后邮件通知
周期性Job通过RecurringJobAttribute特性实现
标注了RecurringJobAttribute的方法将被自动注册为周期性Job并按照周期执行。
RecurringJobAttribute特性说明:
属性 | 说明 |
---|---|
RecurringJobId | 周期性JobId |
Cron | Cron表达式 |
Queue | Queue队列名称 注意要符合Hangfire队列名称的规则 |
TimeZone | 时区设置 |
在方法上使用RecurringJobAttribute周期性Job特性,这些方法将自动注入到Hangfire中
/// <summary>
/// 周期性Job通过特性
/// </summary>
public class RecurringJobService
{
[RecurringJob("0 */1 * * * ?")]
[Queue("recurringjobqueue")]//这个队列要添加到Hangfire的Queues中 不然Job只进入队列无法执行
public void TestJob1()
{
}
[RecurringJob("0 */1 * * * ?", RecurringJobId = "testjob2")]
[Queue("recurringjobqueue")]
public void TestJob2()
{
}
[RecurringJob("0 */1 * * * ?", "China Standard Time", "recurringjobqueue")]
public void TestJob3()
{
}
[RecurringJob("0 */1 * * * ?", "recurringjobqueue")]
public void InstanceTestJob()
{
}
[RecurringJob("0 */1 * * * ?", "UTC", "recurringjobqueue")]
public static void StaticTestJob()
{
}
}
在Hangfire中使用周期性Job
services.AddHangfire(config =>
{
config.UseSqlServerStorage("");
//注入使用了RecurringJobAttribute特性的周期性Job
config.UseRecurringJob();//使用这句话以后将自动把你项目中的使用了RecurringJobAttribute特性的方法注入到Hangfire中
});
app.UseHangfireServer(new BackgroundJobServerOptions()
{
//注意上面写的recurringjobqueue队列名称一定要在这里加入 否则将会出现Job处于Enqueue队列状态而无法执行
Queues = new string[] { "default", "recurringjobqueue" }
});
到此上面的周期性Job就成功了。
.这里你可以使用我发布OpenDeepSpace.Hangfire1.1.0包直接使用,上面的任务执行成功或失败的处理你可以通过实现包中的IJobExecuteResultHandler接口来对成功和失败的Job进行自己的处理,并把实现注入到Service中即可