前言
本來打算昨天都開始寫這篇,就因?yàn)橐研F(tuán)隊的博客整理匯總,一看二哈的博客那么多,一個個復(fù)制粘貼肯定麻煩(其實(shí)是我自己覺得復(fù)制麻煩),所以穿插著寫了個小爬蟲,后續(xù)寫差不多了就拿出來晾晾吧(py菜雞水平)。
之前開發(fā)的時候,忽略了記錄,等到想寫點(diǎn)兒啥跟后臺有關(guān)的東西的時候,還得一點(diǎn)點(diǎn)回憶,最近是因?yàn)橥陆o我說,"哎,每個月把數(shù)據(jù)給我統(tǒng)計下做個界面展示啊"。一想到每個月我要做次操作就頭疼,咦,不對,這不就是寫個定時任務(wù)就搞定了嘛。
Quartz
其實(shí)在選這個定時器的類庫的時候,我在Hangfire兩者間徘徊,后來是想到不管用什么方法什么工具都是次要的,主要看你怎么用,用到哪,圖形界面是需要但不是必要,分秒級別的控制也都是看你自己業(yè)務(wù)需要,定時器就后臺掛起運(yùn)行就行了沒必要讓我看見,想操作了再說吧,就這樣愉快的決定使用Quartz。
首先,依然是在我們Util的工程引入包。
引入完成后,在我們的入口Startup中添加實(shí)例的注冊聲明。
public IServiceProvider ConfigureServices(IServiceCollection services)
{
ServiceInjection.ConfigureRepository(services);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//任務(wù)調(diào)度
services.TryAddSingleton<ISchedulerFactory, StdSchedulerFactory>();
}
SchedulerFactory任務(wù)調(diào)度就好比一個公司的老大,Trigger就是一個項(xiàng)管,Job就是苦逼的碼農(nóng),老大想要一天搞個app,就跟項(xiàng)管說一句,我要一天后要東西,這時候項(xiàng)管心里就有數(shù)了,一天后的那個時間,找到碼農(nóng),直接剝奪他的代碼執(zhí)行,好了app出來了,苦逼的結(jié)束并不意味著真的結(jié)束,這老大一看可以啊,好了,以后每天我要一個成品app,如此循環(huán)往復(fù),項(xiàng)管不厭其煩,碼農(nóng)換了又換(當(dāng)然job不會)。
項(xiàng)管還會有多個,每個項(xiàng)管下面可不止一個碼農(nóng)。
像這樣的情況可能有些夸張,但是類似的情況卻真實(shí)存在。
ok,完了之后,我們來創(chuàng)建一個MyJob。
public class MyJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
return Task.Run(() =>
{
LogUtil.Debug("執(zhí)行MyJob");
});
}
}
之后我們來寫個簡單的QuartzUtil。
public class QuartzUtil
{
private static ISchedulerFactory _schedulerFactory;
private static IScheduler _scheduler;
/// <summary>
/// 添加任務(wù)
/// </summary>
/// <param name="type">類</param>
/// <param name="jobKey">鍵</param>
/// <param name="trigger">觸發(fā)器</param>
public static async Task Add(Type type, JobKey jobKey, ITrigger trigger = null)
{
Init();
_scheduler = await _schedulerFactory.GetScheduler();
await _scheduler.Start();
if (trigger == null)
{
trigger = TriggerBuilder.Create()
.WithIdentity("april.trigger")
.WithDescription("default")
.WithSimpleSchedule(x=>x.WithMisfireHandlingInstructionFireNow().WithRepeatCount(-1))
.Build();
}
var job = JobBuilder.Create(type)
.WithIdentity(jobKey)
.Build();
await _scheduler.ScheduleJob(job, trigger);
}
/// <summary>
/// 恢復(fù)任務(wù)
/// </summary>
/// <param name="jobKey">鍵</param>
public static async Task Resume(JobKey jobKey)
{
Init();
_scheduler = await _schedulerFactory.GetScheduler();
LogUtil.Debug($"恢復(fù)任務(wù){(diào)jobKey.Group},{jobKey.Name}");
await _scheduler.ResumeJob(jobKey);
}
/// <summary>
/// 停止任務(wù)
/// </summary>
/// <param name="jobKey">鍵</param>
public static async Task Stop(JobKey jobKey)
{
Init();
_scheduler = await _schedulerFactory.GetScheduler();
LogUtil.Debug($"暫停任務(wù){(diào)jobKey.Group},{jobKey.Name}");
await _scheduler.PauseJob(jobKey);
}
/// <summary>
/// 初始化
/// </summary>
private static void Init()
{
if (_schedulerFactory == null)
{
_schedulerFactory = AprilConfig.ServiceProvider.GetService<ISchedulerFactory>();
}
}
}
觸發(fā)器的使用,有很多種方式,可以使用簡單的執(zhí)行一次/多久執(zhí)行一次/循環(huán)執(zhí)行幾次等等。
還有可以使用Cron表達(dá)式:
簡單來說,corn從左到右(用空格隔開):秒 分 小時 月份中的日期 月份 星期中的日期 年份,舉個例子,就像開頭說的,讓我每隔一個月執(zhí)行一次統(tǒng)計,寫法就是 0 0 0 1 * ?,當(dāng)然這就有涉及到什么符號的問題了,這種不需要強(qiáng)記,需要的時候查下就行,推薦一個工具站吧,Cron校驗(yàn)工具。
測試
感覺我的博客內(nèi)容好單調(diào),內(nèi)容框架就是開頭,代碼,測試,結(jié)尾,唉
不過做啥東西,測試少不了,最起碼你的東西能用,才說明可行。
我們在Values添加一個方法,這里我們5s一執(zhí)行(懶得等)。
[HttpGet]
[Route("QuartzTest")]
public void QuartzTest(int type)
{
JobKey jobKey = new JobKey("demo","group1");
switch (type)
{
//添加任務(wù)
case 1:
var trigger = TriggerBuilder.Create()
.WithDescription("觸發(fā)器描述")
.WithIdentity("test")
//.WithSchedule(CronScheduleBuilder.CronSchedule("0 0/30 * * * ? *").WithMisfireHandlingInstructionDoNothing())
.WithSimpleSchedule(x=>x.WithIntervalInSeconds(5).RepeatForever().WithMisfireHandlingInstructionIgnoreMisfires())
.Build();
QuartzUtil.Add(typeof(MyJob), jobKey, trigger);
break;
//暫停任務(wù)
case 2:
QuartzUtil.Stop(jobKey);
break;
//恢復(fù)任務(wù)
case 3:
QuartzUtil.Resume(jobKey);
break;
}
}
讓我們來愉快的運(yùn)行吧,記得appsettings配置個路徑訪問白名單。
一番1,2,3輸入完之后,我們來看下日志。
- 執(zhí)行任務(wù)--- ok
- 暫停任務(wù)--- ok
- 恢復(fù)任務(wù)--- ok
問題及解決方法
但是問題出現(xiàn)了,暫?;謴?fù)后,連執(zhí)行了多次(具體看你間隔時間以及你的頻率),這個是有點(diǎn)兒怪異,當(dāng)時我記得這個問題讓我鼓搗了好半天,也是各種查資料查方法,但實(shí)際呢這個是Quartz的保護(hù)機(jī)制,為了防止你的操作是因?yàn)椴豢深A(yù)知的問題導(dǎo)致的,所以有個重做錯過的任務(wù),另外我們的代碼中觸發(fā)器也有這個配置WithMisfireHandlingInstructionIgnoreMisfires。
我們來去掉這個重做機(jī)制并測試。
CronTrigger
| 規(guī)則 | 介紹 |
|---|---|
| withMisfireHandlingInstructionDoNothing | 不觸發(fā)立即執(zhí)行; 等待下次Cron觸發(fā)頻率到達(dá)時刻開始按照Cron頻率依次執(zhí)行 |
| withMisfireHandlingInstructionIgnoreMisfires | 以錯過的第一 個頻率時間立刻開始執(zhí)行; 重做錯過的所有頻率周期后; 當(dāng)下一次觸發(fā)頻率發(fā)生時間大于當(dāng)前時間后,再按照正常的Cron頻率依次執(zhí)行 |
| withMisfireHandlingInstructionFireAndProceed | 以當(dāng)前時間為觸發(fā)頻率立刻觸發(fā)一次執(zhí)行; 然后按照Cron頻率依次執(zhí)行 |
SimpleTrigger
| 規(guī)則 | 介紹 |
|---|---|
| withMisfireHandlingInstructionFireNow | 以當(dāng)前時間為觸發(fā)頻率立即觸發(fā)執(zhí)行; 執(zhí)行至FinalTIme的剩余周期次數(shù);以調(diào)度或恢復(fù)調(diào)度的時刻為基準(zhǔn)的周期頻率,F(xiàn)inalTime根據(jù)剩余次數(shù)和當(dāng)前時間計算得到; 調(diào)整后的FinalTime會略大于根據(jù)starttime計算的到的FinalTime值 |
| withMisfireHandlingInstructionIgnoreMisfires | 以錯過的第一個頻率時間立刻開始執(zhí)行; 重做錯過的所有頻率周期;當(dāng)下一次觸發(fā)頻率發(fā)生時間大于當(dāng)前時間以后,按照Interval的依次執(zhí)行剩下的頻率; 共執(zhí)行RepeatCount+1次 |
| withMisfireHandlingInstructionNextWithExistingCount | 不觸發(fā)立即執(zhí)行; 等待下次觸發(fā)頻率周期時刻,執(zhí)行至FinalTime的剩余周期次數(shù); 以startTime為基準(zhǔn)計算周期頻率,并得到FinalTime; 即使中間出現(xiàn)pause,resume以后保持FinalTime時間不變 |
| withMisfireHandlingInstructionNowWithExistingCount | 以當(dāng)前時間為觸發(fā)頻率立即觸發(fā)執(zhí)行; 執(zhí)行至FinalTIme的剩余周期次數(shù); 以調(diào)度或恢復(fù)調(diào)度的時刻為基準(zhǔn)的周期頻率,F(xiàn)inalTime根據(jù)剩余次數(shù)和當(dāng)前時間計算得到; 調(diào)整后的FinalTime會略大于根據(jù)starttime計算的到的FinalTime值 |
| withMisfireHandlingInstructionNextWithRemainingCount | 不觸發(fā)立即執(zhí)行; 等待下次觸發(fā)頻率周期時刻,執(zhí)行至FinalTime的剩余周期次數(shù); 以startTime為基準(zhǔn)計算周期頻率,并得到FinalTime; 即使中間出現(xiàn)pause,resume以后保持FinalTime時間不變 |
| withMisfireHandlingInstructionNowWithRemainingCount | 以當(dāng)前時間為觸發(fā)頻率立即觸發(fā)執(zhí)行; 執(zhí)行至FinalTIme的剩余周期次數(shù); 以調(diào)度或恢復(fù)調(diào)度的時刻為基準(zhǔn)的周期頻率,F(xiàn)inalTime根據(jù)剩余次數(shù)和當(dāng)前時間計算得到; 調(diào)整后的FinalTime會略大于根據(jù)starttime計算的到的FinalTime值 |
配置規(guī)則介紹參考:https://blog.csdn.net/yangshangwei/article/details/78539433
之前在net framework遇到過一個問題,IIS回收問題,網(wǎng)站在20分鐘無請求后就停了,任務(wù)也緊跟著停了,當(dāng)時的解決方法是做個windows服務(wù)來定時請求網(wǎng)站保持活躍,當(dāng)然也可以通過禁止回收來保持網(wǎng)站一直運(yùn)行。
net core中還沒部署運(yùn)行,如果有相關(guān)問題,后續(xù)也會補(bǔ)充上來一起交流解決。
小結(jié)
定時任務(wù)在一個后臺系統(tǒng)中一般使用場景還算廣泛,主要是sql數(shù)據(jù)統(tǒng)計,sql/文件備份,定時推送等,具體問題具體分析,net core 3.0都已經(jīng)問世了,學(xué)無止境啊。