与quartz.net对比
在项目没有引入Hangfire之前,一直使用的是Quartz.net。Quartz.net在定时任务处理方面优势如下:
- 支持秒级单位的定时任务处理,但是Hangfire只能支持分钟及以上的定时任务处理
原因在于Hangfire用的是开源的NCrontab组件,跟linux上的crontab指令相似。
-
更加复杂的触发器,日历以及任务调度处理
-
可配置的定时任务
但是为什么要换Hangfire? 很大的原因在于项目需要一个后台可监控的应用,不用每次都要从服务器拉取日志查看,在没有ELK的时候相当不方便。Hangfire控制面板不仅提供监控,也可以手动的触发执行定时任务。如果在定时任务处理方面没有很高的要求,比如一定要5s定时执行,Hangfire值得拥有。抛开这些,Hangfire优势太明显了:
-
持久化保存任务、队列、统计信息
-
重试机制
-
多语言支持
-
支持任务取消
-
支持按指定
Job Queue
处理任务 -
服务器端工作线程可控,即job执行并发数控制
-
分布式部署,支持高可用
-
良好的扩展性,如支持IOC、Hangfire Dashboard授权控制、Asp.net Core、持久化存储等
这里演示在asp.net core使用Redis作为Hangfire的持久化存储
1、引用包
Hangfire.AspNetCore
Hangfire.Redis.StackExchange
2、注入hangfire及配置
这里说明下 services.AddHostedService<MyJob1>();是定时任务,继承自BackgroundService。
然后在configure中引入hangfire。如果在这里不引入队列的名字,会导致添加的任务无法加入到队列。
app.UseHangfireDashboard();表示使用看板功能。
3、普通使用,比如添加任务到队列或者添加延时任务,基本是一样的。
这里在接口中直接演示,直接使用BackgroundJob.Enqueue将方法添加到队列。
注意队列名称要使用小写,被添加到队列中的方法需要是Public的。
4、定时任务
定义定时任务配置类
/// <summary> /// 工作任务配置 /// </summary> public class WorkerConfig { /// <summary> /// 轮询秒数 /// </summary> public int IntervalSecond { get; set; } /// <summary> /// 工作唯一编号 /// </summary> public string WorkerId { get; set; } /// <summary> /// 队列名称 /// </summary> public string QueuesName { get; set; } /// <summary> /// 表达式 /// </summary> public string Cron { get; set; } }
定义需要定时执行的接口方法
/// <summary> /// 所有的后台工作者类都应实现该接口 /// </summary> public interface IBackgroundWorkerDo { /// <summary> /// 执行具体的任务 /// </summary> /// <param name="context"></param> /// <returns></returns> Task DoWorkAsync(PerformContext context); }
定义hangfire基类,定时任务的类都继承该类。
public abstract class HangfireWorkerPxoxy:BackgroundService { private readonly IConfiguration _appConfiguration; private readonly WorkerConfig _config; public HangfireWorkerPxoxy(IConfiguration appConfiguration, WorkerConfig Config) { _appConfiguration = appConfiguration; _config = Config; } public void Excete<T>() where T : IBackgroundWorkerDo { var appsetting = _appConfiguration.GetSection("Appsetting").Get<Appsetting>(); var options = new RedisStorageOptions { Db = appsetting.RedisConfig.Defaultdatabase, SucceededListSize = 50000, DeletedListSize = 50000, ExpiryCheckInterval = TimeSpan.FromHours(1), InvisibilityTimeout = TimeSpan.FromHours(3) }; var redisString = appsetting.RedisConfig.HostName + ":" + appsetting.RedisConfig.Port + ",defaultdatabase="+ appsetting.RedisConfig.Defaultdatabase; JobStorage.Current = new RedisStorage(redisString); var manager = new RecurringJobManager(JobStorage.Current); string workerId = _config.WorkerId; string cron = string.IsNullOrEmpty(_config.Cron) ? Cron.MinuteInterval(_config.IntervalSecond / 60) : _config.Cron; manager.RemoveIfExists(workerId); manager.AddOrUpdate(workerId, Job.FromExpression<T>((t) => t.DoWorkAsync(null)), cron, TimeZoneInfo.Local, string.IsNullOrEmpty(_config.QueuesName) ? "default" : _config.QueuesName); }
定义最终的定时任务类
/// <summary> /// 定时任务 /// </summary> class MyJob1 : HangfireWorkerPxoxy,IBackgroundWorkerDo { private readonly ILogger _logger; public MyJob1(IConfiguration _appConfiguration, ILogger<MyJob1> logger) : base(_appConfiguration, new WorkerConfig { IntervalSecond = 60 * 1, WorkerId = "RealWorker", QueuesName = "queue1" }) { _logger = logger; } /// <summary> /// 添加同步sku库存任务 /// </summary> public async Task DoWorkAsync(PerformContext context) { var temp = await Task.FromResult(0); if (context == null) { return ; } _logger.LogInformation($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始添加同步数据任务 ..."); Thread.Sleep(1000*10); _logger.LogInformation($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 结束添加同步数据任务 ..."); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var temp= await Task.FromResult(0); Excete<MyJob1>(); } }
注入定时任务类,services.AddHostedService<MyJob1>();
最后看下效果。
ok,大功告成。