3分鐘掌握Quartz.net分散式定時任務的姿勢
引言
長話短說,今天聊一聊分散式定時任務,我的流水賬筆記:
- ASP.NET Core+Quartz.Net實現web定時任務
- AspNetCore結合Redis實踐訊息佇列
細心朋友稍一分析,就知道還有問題:
水平擴充套件後的WebApp的Quartz.net定時任務會多次觸發,
因為webapp例項使用的是預設的RAMJobStore
, 多例項在記憶體中都維護了Job和Trigger的副本.
我的定時任務是同步任務,多次執行倒是沒有太大問題,但對於特定業務的定時任務, 多次執行可能是致命問題。
基於此,來看看Quartz.net 分散式定時任務的姿勢
AdoJobStore
很明顯,水平擴充套件的多例項需要一個 獨立於web例項的機制來儲存Job和Trigger.
Quartz.NET提供ADO.NET JobStore來儲存任務資料。
- 先使用SQL指令碼在資料庫中生成指定的表結構
執行指令碼之後,會看到資料庫中多出幾個以 QRTZ_開頭的表
- 配置Quartz.net使用AdoJobStore
可採用編碼形式或者 quartz.config形式新增配置
快速實踐
1. 預先生成Job、Trigger表
從https://github.com/quartznet/quartznet/tree/master/database/tables 下載合適的資料庫表指令碼, 生成指定的表結構
2. 新增AdoJobStore
本次使用編碼方式新增AdoJobStore配置。
首次啟動會將程式碼中Job和Trigger持久化到sqlite,後面就直接從sqlite中載入Job和Trigger
using System; using System.Collections.Specialized; using System.Data; using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; using Quartz; using Quartz.Impl; using Quartz.Impl.AdoJobStore.Common; using Quartz.Spi; namespace EqidManager { using IOCContainer = IServiceProvider; 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); DbProvider.RegisterDbMetadata("sqlite-custom", new DbMetadata() { AssemblyName = typeof(SqliteConnection).Assembly.GetName().Name, ConnectionType = typeof(SqliteConnection), CommandType = typeof(SqliteCommand), ParameterType = typeof(SqliteParameter), ParameterDbType = typeof(DbType), ParameterDbTypePropertyName = "DbType", ParameterNamePrefix = "@", ExceptionType = typeof(SqliteException), BindByName = true }); var properties = new NameValueCollection { ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", ["quartz.jobStore.useProperties"] = "true", ["quartz.jobStore.dataSource"] = "default", ["quartz.jobStore.tablePrefix"] = "QRTZ_", ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SQLiteDelegate, Quartz", ["quartz.dataSource.default.provider"] = "sqlite-custom", ["quartz.dataSource.default.connectionString"] = "Data Source=EqidManager.db", ["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz", ["quartz.serializer.type"] = "binary" }; var schedulerFactory = new StdSchedulerFactory(properties); Scheduler = schedulerFactory.GetScheduler().Result; Scheduler.JobFactory = iocJobfactory; } public async Task<IScheduler> ScheduleJob() { var _eqidCounterResetJob = JobBuilder.Create<EqidCounterResetJob>() .WithIdentity("EqidCounterResetJob") .Build(); var _eqidCounterResetJobTrigger = TriggerBuilder.Create() .WithIdentity("EqidCounterResetCron") .StartNow() //每天凌晨0s .WithCronSchedule("0 0 0 * * ?") Seconds,Minutes,Hours,Day-of-Month,Month,Day-of-Week,Year(optional field) .Build(); // 這裡一定要先判斷是否已經從SQlite中載入了Job和Trigger if (!await Scheduler.CheckExists(new JobKey("EqidCounterResetJob")) && !await Scheduler.CheckExists(new TriggerKey("EqidCounterResetCron"))) { await Scheduler.ScheduleJob(_eqidCounterResetJob, _eqidCounterResetJobTrigger); } await Scheduler.Start(); return Scheduler; } public void EndScheduler() { if (Scheduler == null) { return; } if (Scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000)) Scheduler = null; else { } _logger.LogError("Schedule job upload as application stopped"); } } }
上面是Quartz.NET 從sqlite中載入Job和Trigger的核心程式碼
這裡要提示兩點:
①. IOCJobFactory 是自定義JobFactory,目的是與ASP.NET Core原生依賴注入結合
②. 在排程任務的時候, 要先判斷是否已經從sqlite載入了Job和Trigger
3.新增Quartz.Net UI輪子
附贈Quartz.NET的排程UI: CrystalQuartz,方便在介面管理排程任務
① Install-Package CrystalQuartz.AspNetCore -IncludePrerelease
② Startup啟用CrystalQuartz
using CrystalQuartz.AspNetCore;
/*
* app is IAppBuilder
* scheduler is your IScheduler (local or remote)
*/
var quartz = app.ApplicationServices.GetRequiredService<QuartzStartup>();
var _schedule = await quartz.ScheduleJob();
app.UseCrystalQuartz(() => scheduler);
③ 在localhost:YOUR_PORT/quartz地址檢視排程
總結輸出
- Quartz.net以AdoJobStore支撐分散式定時任務,解決多例項多次觸發的問題
- 快速丟擲輪子:Quartz.Net UI庫
- https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/job-stores.html
- https://github.com/guryanovev/CrystalQuartz