Master the posture of Quartz.net distributed timing tasks in 3 minutes

introduction

To make a long story short, talk about distributed timing tasks today. My journal notes:

  • ASP.NET Core + Quartz.Net implements web timing tasks
  • AspNetCore combined with Redis practice message queue

After careful analysis, friends know that there is still a problem: the
horizontally expanded WebApp Quartz.net scheduled task will be triggered multiple times,
because the webapp instance uses the default, RAMJobStoreand multiple instances maintain copies of Job and Trigger in memory. .

My scheduled task is a synchronous task. It is not a big problem to execute it multiple times, but for a scheduled task of a specific business, multiple executions may be a fatal problem.

Based on this, take a look at the pose of Quartz.net distributed timing tasks

AdoJobStore

Obviously, horizontally expanding multi-instances require a mechanism independent of web instances to store Job and Trigger.

Quartz.NET provides ADO.NET JobStore to store job data.

  1. First use SQL script to generate the specified table structure in the database

After executing the script, you will see a few more tables starting with QRTZ_ in the database

  1. Configure Quartz.net to use AdoJobStore

The configuration can be added in the form of encoding or quartz.config

Quick practice

1. Generate Job and Trigger tables in advance

Download the appropriate database table script from https://github.com/quartznet/quartznet/tree/master/database/tables to generate the specified table structure

2. Add AdoJobStore

This time, the AdoJobStore configuration is added using encoding.
The first start will persist the Job and Trigger in the code to sqlite, and then directly load the Job and Trigger from sqlite

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");
        }
    }
}

The above is the core code of Quartz.NET loading Job and Trigger from sqlite

Here are two tips:

①. IOCJobFactory is a custom JobFactory, the purpose is to combine with ASP.NET Core native dependency injection
②. When scheduling tasks, we must first determine whether Job and Trigger have been loaded from sqlite

3. Add Quartz.Net UI wheel

Comes with Quartz.NET's scheduling UI: CrystalQuartz, to facilitate scheduling tasks on the interface
① Install-Package CrystalQuartz.AspNetCore -IncludePrerelease
② Startup enables 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);

③ Check the schedule at localhost: YOUR_PORT / quartz address

Summary output

  1. Quartz.net uses AdoJobStore to support distributed timing tasks, solving the problem of multiple instances triggered multiple times
  2. Quickly throw the wheel: Quartz.Net UI library

Guess you like

Origin www.cnblogs.com/JulianHuang/p/12720436.html