## 一:背景 ### 1. 講故事 前些天有位朋友找到我,說他的程式跑著跑著就崩潰了,讓我看下怎麼回事,其實沒怎麼回事,抓它的 crash dump 就好,具體怎麼抓也是被問到的一個高頻問題,這裡再補一下鏈接: [.NET程式崩潰了怎麼抓 Dump ? 我總結了三種方案] https://www. ...
Quartz.NET 官網
Quartz.net是什麼
Quartz.NET 是一個功能齊全的開源作業調度系統,他的前身來源於java的Quartz.Quartz.net安裝和使用
基於visual studio引用安裝,其他IDE類似,或者下載DLL手動引用也是可以的;運行環境基於.net core,源.net程式類似Quartz.net的架構和關鍵屬性以及方法
三個主要的概念
- scheduler 作業調度,作業計劃在給定觸發器發生時運行,實際就是領導
- job 作業,實現簡單 IJob 介面的任何 .NET 類,實際就是幹活的員工
- trigger 偵聽器,負責捕獲調度事件以監視或控製作業,實際就是監工
可以這樣理解:
監工發現員工偷懶了,報告給領導,領導知道後,給員工派了很多活,導致了員工天天996. 大概是這麼個關係; 同時呢,一個員工可以被多個監工監理,同理一個監工也可以監理多個員工,他們是多對多的關係;多個員工也可以共屬於一個領導,當然也可以一個領導只有一個員工,他們直接也是多對多的關係Quartz.net的一些關鍵屬性
類型 | |
ISchedulerFactory | SchedulerBuilder的工廠類 |
IScheduler | 用於與調度程式交互的主要 API |
SchedulerBuilder | 用於定義/構建調度程式實例,需要 Quartz 3.1 或更高版本 |
IJobFactory | JobBuilder的工廠類 |
IJob | 由您希望由調度程式執行的組件實現的介面 |
IJobDetail | 用於定義作業的實例 |
JobBuilder | 用於定義/構建 JobDetail 實例,這些實例定義作業的實例 |
TriggerBuilder | 用於定義/構建觸發器實例 |
ITrigger | 定義執行給定作業的計劃的一個組件,作業可以有多個關聯的觸發器 |
ListenerManager | 偵聽器事件,例如:執行job工作之前,之後觸發等等,同時也可用於觸發器偵聽 |
IServiceCollectionQuartzConfigurator 參數
Scheduler Name | 調度作業的名稱 |
Scheduler Id | SchedulerId |
Max Batch Size | 同時執行job的最大數量 |
InterruptJobsOnShutdown | |
InterruptJobsOnShutdownWithWait | |
BatchTriggerAcquisitionFireAheadTimeWindow |
在通用host或者webhost中的最佳實踐
通用host或者webhost代碼是一樣的
執行流程
- 在通用主機服務中註入服務AddQuartz,AddQuartzHostedService
- 在AddQuartz中配置調度作業的基本屬性(SchedulerId等等)和調度器以及作業(ScheduleJob,AddJob,AddTrigger);可以在這個地方寫入所有的調度作業,也可以寫入一個initjob作業,在主機完全啟動5秒後執行相應的業務(可規避掉某些依賴服務未啟動的問題)
- 在initjob中,初始化其他定時任務。官網介紹job只能有一個無參的構造函數,但我親測可以註入(笑臉)
- 關於job和reigger的具體參數,可查看官網
如下
以下代碼和執行結果,其中執行順序一目瞭然代碼
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
//通用主機配置
var build = Host.CreateDefaultBuilder(args)
.ConfigureServices((host, services) =>
{
Console.WriteLine("--------1");
//調度作業的唯一id的唯一標識,用於集群搭建cluster
q.SchedulerId = "SchedulerId_01";
//配置Quartz服務
services.AddQuartz(q =>
{
Console.WriteLine("--------2");
//依賴註入,ISchedulerFactory,Ijob等等
q.UseMicrosoftDependencyInjectionJobFactory();
//方法一和方法二使用不同方法的寫法,本質基本是一樣的
//方法一
q.ScheduleJob<InitJob>(
trigger =>
{
Console.WriteLine("--------33");
//WithIdentity 綁定觸發器或者job的唯一屬性和組
//TriggerKey,JobKey 都是代表唯一個屬性和組
trigger.WithIdentity(new TriggerKey("trigger1", "triggergroup1"))
.WithSimpleSchedule(x => x.WithIntervalInSeconds(5))
// .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(5)))
// .WithDailyTimeIntervalSchedule(x => x.WithInterval(10, IntervalUnit.Second))
.WithDescription("init 描述");
},
jobConfigure =>
{
Console.WriteLine("--------44");
jobConfigure.WithIdentity(new JobKey("Init1", "jobgroup1"));
}
);
//方法二
//q.AddJob<InitJob>(opts =>
//{
// Console.WriteLine("--------3");
// opts.WithIdentity(new JobKey("Init1", "jobgroup1"));
//});
//q.AddTrigger(opts =>
//{
// Console.WriteLine("--------4");
// //將job添加至觸發器中
// opts.ForJob(new JobKey("Init1", "jobgroup1"))
// .WithIdentity("trigger1", "triggergroup1")
// .WithSimpleSchedule(x =>
// {
// Console.WriteLine("--------6");
// x.WithIntervalInSeconds(5);
// //.RepeatForever();
// //.WithRepeatCount(5);
// });
//});
});
services.AddQuartzHostedService(options =>
{
options.WaitForJobsToComplete = true;
});
}).Build();
//var schedulerFactory = build.Services.GetService<ISchedulerFactory>();
//var scheduler = schedulerFactory.GetScheduler();
build.Run();
Console.WriteLine("--------7");
}
}
public class SampleJob : IJob
{
public SampleJob(ISchedulerFactory schedulerFactory, IJobFactory jobFactory)
{
Console.WriteLine("--------8");
}
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("--------9");
context.JobDetail.JobDataMap.GetString("我是sample的job數據key");
Console.WriteLine($"我是sample的job數據key: {context.JobDetail.JobDataMap.GetString("我是sample的job數據key")}");
Console.WriteLine($"我是sample的Trigger數據key: {context.MergedJobDataMap.GetString("我是sample的Trigger數據key")}");
}
}
public class InitJob : IJob
{
public ISchedulerFactory _schedulerFactory;
public IJobFactory _jobFactory;
public InitJob(ISchedulerFactory schedulerFactory, IJobFactory jobFactory)
{
Console.WriteLine("--------12");
_schedulerFactory = schedulerFactory;
_jobFactory = jobFactory;
}
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("--------13");
Console.WriteLine("InitJob Execute " + Random.Shared.Next(0, 100));
//創建job
IJobDetail job = JobBuilder.Create<SampleJob>()
//寫入參數
.UsingJobData("我是sample的job數據key", "我是sample的job數據value")
.WithIdentity("sample1", "jobgroup1").Build();
//創建觸發器
ITrigger trigger = TriggerBuilder.Create()
.UsingJobData("我是sample的Trigger數據key", "我是sample的Trigger數據value")
.WithIdentity("trigger_sample1", "triggergroup1")
.WithDescription("我是描述")
//通過corn符號來創建觸發器
//.WithCronSchedule(taskOptions.CronExpression)
.WithSimpleSchedule(x =>
x.WithIntervalInSeconds(5) //5秒後執行
.RepeatForever() //重覆
)
.Build();
//通過工廠獲取一個作業調度
var scheduler = await _schedulerFactory.GetScheduler();
//綁定一個job的事件偵聽器,從執行順序上看 new JobListen是一個單例類
scheduler.ListenerManager.AddJobListener(new JobListen(), KeyMatcher<JobKey>.KeyEquals(new JobKey("sample1", "jobgroup1")));
//將作業和從觸發器綁定至作業調度上
await scheduler.ScheduleJob(job, trigger);
//啟動作業調度
await scheduler.Start();
Console.WriteLine("--------14");
}
}
//作業偵聽器
public class JobListen : JobListenerSupport
{
public JobListen()
{
Console.WriteLine("--------20");
}
public override string Name { get { return "JobListen20"; } }
//調用job之前執行
public override Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken)
{
Console.WriteLine("--------21");
return base.JobToBeExecuted(context, cancellationToken);
}
}
//日誌組件
public class ConsoleLogProvider : ILogProvider
{
public Logger GetLogger(string name)
{
return (level, func, exception, parameters) =>
{
if (level >= LogLevel.Info && func != null)
{
Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
}
return true;
};
}
public IDisposable OpenNestedContext(string message)
{
throw new NotImplementedException();
}
public IDisposable OpenMappedContext(string key, object value, bool destructure = false)
{
throw new NotImplementedException();
}
}