ASP.NET Core 2.2+Quartz.Net 实现Web定时任务
作者:Julian_醬
鏈接:http://www.cnblogs.com/mi12205599/p/10361763.html
作為一枚后端程序狗,項目實踐常遇到定時任務(wù)的工作,最容易想到的的思路就是利用Windows計劃任務(wù)/wndows service程序/Crontab程序等主機(jī)方法在主機(jī)上部署定時任務(wù)程序/腳本。
但是很多時候,若使用的是共享主機(jī)或者受控主機(jī),這些主機(jī)不允許你私自安裝exe程序、Windows服務(wù)程序。
碼甲會想到在web程序中做定時任務(wù), 目前有兩個方向:
1、AspNetCore自帶的HostService, 這是一個輕量級的后臺服務(wù), 需要搭配timer完成定時任務(wù)
2、老牌Quartz.Net組件,支持復(fù)雜靈活的Scheduling、支持ADO/RAM Job任務(wù)存儲、支持集群、支持監(jiān)聽、支持插件。
此處我們的項目使用稍復(fù)雜的Quartz.net實現(xiàn)Web定時任務(wù)。
項目背景
最近需要做一個計數(shù)程序:采用redis計數(shù),設(shè)定每小時將當(dāng)日累積數(shù)據(jù)持久化到關(guān)系型數(shù)據(jù)庫sqlite。
添加Quartz.Net Nuget 依賴包:<PackageReference Include="Quartz" Version="3.0.6" />
1、定義定時任務(wù)內(nèi)容: Job
2、設(shè)置觸發(fā)條件: Trigger
3、將Quartz.Net集成進(jìn)AspNet Core
頭腦風(fēng)暴
IScheduler類包裝了上述背景需要完成的第①②點工作 ,SimpleJobFactory定義了生成指定的Job任務(wù)的過程,這個行為是利用反射機(jī)制調(diào)用無參構(gòu)造函數(shù)構(gòu)造出的Job實例。
下面是源碼:
using System;
using Quartz.Logging;
using Quartz.Spi;
using Quartz.Util;
namespace Quartz.Simpl
{
? ?/// <summary>
? ?/// The default JobFactory used by Quartz - simply calls
? ?/// <see cref="ObjectUtils.InstantiateType{T}" /> on the job class.
? ?/// </summary>
? ?/// <seealso cref="IJobFactory" />
? ?/// <seealso cref="PropertySettingJobFactory" />
? ?/// <author>James House</author>
? ?/// <author>Marko Lahma (.NET)</author>
? ?public class SimpleJobFactory : IJobFactory
? ?{
? ? ? ?private static readonly ILog log = LogProvider.GetLogger(typeof (SimpleJobFactory));
? ? ? ?/// <summary>
? ? ? ?/// Called by the scheduler at the time of the trigger firing, in order to
? ? ? ?/// produce a <see cref="IJob" /> instance on which to call Execute.
? ? ? ?/// </summary>
? ? ? ?/// <remarks>
? ? ? ?/// It should be extremely rare for this method to throw an exception -
? ? ? ?/// basically only the case where there is no way at all to instantiate
? ? ? ?/// and prepare the Job for execution. ?When the exception is thrown, the
? ? ? ?/// Scheduler will move all triggers associated with the Job into the
? ? ? ?/// <see cref="TriggerState.Error" /> state, which will require human
? ? ? ?/// intervention (e.g. an application restart after fixing whatever
? ? ? ?/// configuration problem led to the issue with instantiating the Job).
? ? ? ?/// </remarks>
? ? ? ?/// <param name="bundle">The TriggerFiredBundle from which the <see cref="IJobDetail" />
? ? ? ?/// ? and other info relating to the trigger firing can be obtained.</param>
? ? ? ?/// <param name="scheduler"></param>
? ? ? ?/// <returns>the newly instantiated Job</returns>
? ? ? ?/// <throws> ?SchedulerException if there is a problem instantiating the Job. </throws>
? ? ? ?public virtual IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
? ? ? ?{
? ? ? ? ? ?IJobDetail jobDetail = bundle.JobDetail;
? ? ? ? ? ?Type jobType = jobDetail.JobType;
? ? ? ? ? ?try
? ? ? ? ? ?{
? ? ? ? ? ? ? ?if (log.IsDebugEnabled())
? ? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ?log.Debug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?return ObjectUtils.InstantiateType<IJob>(jobType);
? ? ? ? ? ?}
? ? ? ? ? ?catch (Exception e)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
? ? ? ? ? ? ? ?throw se;
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?/// <summary>
? ? ? ?/// Allows the job factory to destroy/cleanup the job if needed.
? ? ? ?/// No-op when using SimpleJobFactory.
? ? ? ?/// </summary>
? ? ? ?public virtual void ReturnJob(IJob job)
? ? ? ?{
? ? ? ? ? ?var disposable = job as IDisposable;
? ? ? ? ? ?disposable?.Dispose();
? ? ? ?}
? ?}
}
public static T InstantiateType<T>(Type type)
{
? ? if(type == null)
? ? {
? ? ? ? ?throw new ArgumentNullException(nameof(type), "Cannot instantiate null");
? ? }
? ? ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
? ? if (ci == null)
? ? {
? ? ? ? ?throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
? ? }
? ? return (T) ci.Invoke(new object[0]);
}
很多時候,定義的Job任務(wù)依賴了其他組件(Job實例化時多參),此時默認(rèn)的SimpleJobFactory不能滿足實例化要求, 需要考慮將Job任務(wù)作為依賴注入組件,加入依賴注入容器。
關(guān)鍵思路:
① IScheduler 開放了JobFactory 屬性,便于你控制Job任務(wù)的實例化方式;
JobFactories may be of use to those wishing to have their application produce IJob instances via some special mechanism, such as to give the opportunity for dependency injection
②AspNet Core的服務(wù)架構(gòu)是以依賴注入為基礎(chǔ)的,利用ASPNET Core已有的依賴注入容器IServiceProvider管理Job任務(wù)的創(chuàng)建過程。
編碼實踐
1、定義Job內(nèi)容
// -------每小時將redis數(shù)據(jù)持久化到sqlite, 每日凌晨跳針,持久化昨天全天數(shù)據(jù)-
public class UsageCounterSyncJob : IJob
{
? ? ? ?private readonly EqidDbContext _context;
? ? ? ?private readonly IDatabase _redisDB1;
? ? ? ?private readonly ILogger _logger;
? ? ? ?public UsageCounterSyncJob(EqidDbContext context, RedisDatabase redisCache, ILoggerFactory loggerFactory)
? ? ? ?{
? ? ? ? ? ?_context = context;
? ? ? ? ? ?_redisDB1 = redisCache[1];
? ? ? ? ? ?_logger = loggerFactory.CreateLogger<UsageCounterSyncJob>();
? ? ? ?}
? ? ? ?public async Task Execute(IJobExecutionContext context)
? ? ? ?{
? ? ? ? ? ?// 觸發(fā)時間在凌晨,則同步昨天的計數(shù)
? ? ? ? ? ?var _day = DateTime.Now.ToString("yyyyMMdd");
? ? ? ? ? ?if (context.FireTimeUtc.LocalDateTime.Hour == 0)
? ? ? ? ? ? ? ?_day = DateTime.Now.AddDays(-1).ToString("yyyyMMdd");
? ? ? ? ? ?await SyncRedisCounter(_day);
? ? ? ? ? ?_logger.LogInformation("[UsageCounterSyncJob] Schedule job executed.");
? ? ? ?}
? ? ? ?......
}
2、注冊Job和Trigger
namespace EqidManager{
? ?using IOCContainer = IServiceProvider;
? ?// Quartz.Net啟動后注冊job和trigger
? ?public class QuartzStartup
? ?{
? ? ? ?public IScheduler _scheduler { get; set; }
? ? ? ?private readonly ILogger _logger;
? ? ? ?private readonly IJobFactory iocJobfactory;
? ? ? ?public QuartzStartup(IOCContainer IocContainer, ILoggerFactory loggerFactory)
? ? ? ?{
? ? ? ? ? ?_logger = loggerFactory.CreateLogger<QuartzStartup>();
? ? ? ? ? ?iocJobfactory = new IOCJobFactory(IocContainer);
? ? ? ? ? ?var schedulerFactory = new StdSchedulerFactory();
? ? ? ? ? ?_scheduler = schedulerFactory.GetScheduler().Result;
? ? ? ? ? ?_scheduler.JobFactory = iocJobfactory;
? ? ? ?}
? ? ? ?public void Start()
? ? ? ?{
? ? ? ? ? ?_logger.LogInformation("Schedule job load as application start.");
? ? ? ? ? ?_scheduler.Start().Wait();
? ? ? ? ? ?var UsageCounterSyncJob = JobBuilder.Create<UsageCounterSyncJob>()
? ? ? ? ? ? ? .WithIdentity("UsageCounterSyncJob")
? ? ? ? ? ? ? .Build();
? ? ? ? ? ?var UsageCounterSyncJobTrigger = TriggerBuilder.Create()
? ? ? ? ? ? ? ?.WithIdentity("UsageCounterSyncCron")
? ? ? ? ? ? ? ?.StartNow()
? ? ? ? ? ? ? ?// 每隔一小時同步一次
? ? ? ? ? ? ? ?.WithCronSchedule("0 0 * * * ?") ? ? ?// Seconds,Minutes,Hours,Day-of-Month,Month,Day-of-Week,Year(optional field)
? ? ? ? ? ? ? ?.Build();
? ? ? ? ? ?_scheduler.ScheduleJob(UsageCounterSyncJob, UsageCounterSyncJobTrigger).Wait();
? ? ? ? ? ?_scheduler.TriggerJob(new JobKey("UsageCounterSyncJob"));
? ? ? ?}
? ? ? ?public void Stop()
? ? ? ?{
? ? ? ? ? ?if (_scheduler == null)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?return;
? ? ? ? ? ?}
? ? ? ? ? ?if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
? ? ? ? ? ? ? ?_scheduler = null;
? ? ? ? ? ?else
? ? ? ? ? ?{
? ? ? ? ? ?}
? ? ? ? ? ?_logger.LogCritical("Schedule job upload as application stopped");
? ? ? ?}
? ?}
? ?/// <summary>
? ?/// IOCJobFactory :實現(xiàn)在Timer觸發(fā)的時候注入生成對應(yīng)的Job組件
? ?/// </summary>
? ?public class IOCJobFactory : IJobFactory
? ?{
? ? ? ?protected readonly IOCContainer Container;
? ? ? ?public IOCJobFactory(IOCContainer container)
? ? ? ?{
? ? ? ? ? ?Container = container;
? ? ? ?}
? ? ? ?//Called by the scheduler at the time of the trigger firing, in order to produce
? ? ? ?//a Quartz.IJob instance on which to call Execute.
? ? ? ?public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
? ? ? ?{
? ? ? ? ? ?return Container.GetService(bundle.JobDetail.JobType) as IJob;
? ? ? ?}
? ? ? ?// Allows the job factory to destroy/cleanup the job if needed.
? ? ? ?public void ReturnJob(IJob job)
? ? ? ?{
? ? ? ?}
? ?}
}
3、結(jié)合ASpNet Core 注入組件;綁定Quartz.Net
//-------------------------------截取自Startup文件---------------......
services.AddTransient<UsageCounterSyncJob>(); ? ?
// 這里使用瞬時依賴注入
services.AddSingleton<QuartzStartup>();
......
// 綁定Quartz.Net
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IApplicationLifetime lifetime, ILoggerFactory loggerFactory)
{
? ? var quartz = app.ApplicationServices.GetRequiredService<QuartzStartup>();
? ? lifetime.ApplicationStarted.Register(quartz.Start);
? ? lifetime.ApplicationStopped.Register(quartz.Stop);
}
附:IIS 網(wǎng)站低頻訪問導(dǎo)致工作進(jìn)程進(jìn)入閑置狀態(tài)的 解決辦法
IIS為網(wǎng)站默認(rèn)設(shè)定了20min閑置超時時間:20分鐘內(nèi)沒有處理請求、也沒有收到新的請求,工作進(jìn)程就進(jìn)入閑置狀態(tài)。
IIS上低頻web訪問會造成工作進(jìn)程關(guān)閉,此時應(yīng)用程序池回收,Timer等線程資源會被銷毀;當(dāng)工作進(jìn)程重新運作,Timer可能會重新生成起效, 但我們的設(shè)定的定時Job可能沒有按需正確執(zhí)行。
故為在IIS網(wǎng)站實現(xiàn)低頻web訪問下的定時任務(wù):
設(shè)置Idle TimeOut =0;同時將【應(yīng)用程序池】->【正在回收】->不勾選【回收條件】? ? ??
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 2.2+Quartz.Net 实现Web定时任务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站锦囊(逐渐更新中)
- 下一篇: QQ概念版(WPF制作)