作者:Julian_醬
鏈接:http://www.cnblogs.com/mi12205599/p/10361763.html
作為一枚后端程序狗,項目實踐常遇到定時任務的工作,最容易想到的的思路就是利用Windows計劃任務/wndows service程序/Crontab程序等主機方法在主機上部署定時任務程序/腳本。
但是很多時候,若使用的是共享主機或者受控主機,這些主機不允許你私自安裝exe程序、Windows服務程序。
碼甲會想到在web程序中做定時任務, 目前有兩個方向:
1、AspNetCore自帶的HostService, 這是一個輕量級的后臺服務, 需要搭配timer完成定時任務
2、老牌Quartz.Net組件,支持復雜靈活的Scheduling、支持ADO/RAM Job任務存儲、支持集群、支持監聽、支持插件。
此處我們的項目使用稍復雜的Quartz.net實現Web定時任務。
項目背景
最近需要做一個計數程序:采用redis計數,設定每小時將當日累積數據持久化到關系型數據庫sqlite。
添加Quartz.Net Nuget 依賴包:<PackageReference Include="Quartz" Version="3.0.6" />
1、定義定時任務內容: Job
2、設置觸發條件: Trigger
3、將Quartz.Net集成進AspNet Core
頭腦風暴
IScheduler類包裝了上述背景需要完成的第①②點工作 ,SimpleJobFactory定義了生成指定的Job任務的過程,這個行為是利用反射機制調用無參構造函數構造出的Job實例。
下面是源碼:
//----------------選自Quartz.Simpl.SimpleJobFactory類-----------
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();
? ? ? ?}
? ?}
}
//------------------節選自Quartz.Util.ObjectUtils類------------
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任務依賴了其他組件(Job實例化時多參),此時默認的SimpleJobFactory不能滿足實例化要求, 需要考慮將Job任務作為依賴注入組件,加入依賴注入容器。
關鍵思路:
① IScheduler 開放了JobFactory 屬性,便于你控制Job任務的實例化方式;
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的服務架構是以依賴注入為基礎的,利用ASPNET Core已有的依賴注入容器IServiceProvider管理Job任務的創建過程。
編碼實踐
1、定義Job內容
// -------每小時將redis數據持久化到sqlite, 每日凌晨跳針,持久化昨天全天數據-
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)
? ? ? ?{
? ? ? ? ? ?// 觸發時間在凌晨,則同步昨天的計數
? ? ? ? ? ?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 :實現在Timer觸發的時候注入生成對應的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、結合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 網站低頻訪問導致工作進程進入閑置狀態的 解決辦法
IIS為網站默認設定了20min閑置超時時間:20分鐘內沒有處理請求、也沒有收到新的請求,工作進程就進入閑置狀態。
IIS上低頻web訪問會造成工作進程關閉,此時應用程序池回收,Timer等線程資源會被銷毀;當工作進程重新運作,Timer可能會重新生成起效, 但我們的設定的定時Job可能沒有按需正確執行。
故為在IIS網站實現低頻web訪問下的定時任務:
設置Idle TimeOut =0;同時將【應用程序池】->【正在回收】->不勾選【回收條件】? ? ??