SGwanmin

在学习C语言和数据结构,,, 日了,上学时候没感觉啥的,学的越多发现得调头翻大学教材了,啊,西八!

导航

Scheduler-Quartz.Net 使用原理及教程

Scheduler-Quartz.Net 使用原理及教程

1.简介

这一篇会相对比较跳跃和简洁,一方面是官方文档很详尽,另外就是Scheduler最重要的是理解其中的思想!!!

Quartz.NET是一个开源的job调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。它具有良好的灵活性而且配置操作简单。你能够用它执行一个Job来实现简单或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。

它有如下几个特点:

  • API 操作简单,只要几行简单的代码你就可以在应用程序里面实现自己的Job调度,并实时监视Job的执行情况

  • 触发器功能强大,比 Windows 的任务计划提供更细的触发粒度,你可以使用“Cron表达式(后文将介绍)”来实现如:每周星期一到星期五 8:00am,5:00pm(工作时间) 执行某一件任务

  • 良好的可扩展性,它基于接口编程,你可以实现自己的 Schedule 调度器,Job 作业,以及 Trigger 触发器等

  • Job可以保存在 RAM 中,也可以持久化到数据库,支持多种数据库类型:SqlServer、Oracle、MySql等

  • 集群,这是一个高级应用,可以在多台计算机之间创建负载平衡、容错处理

详情请查看:Quartz.net官网

官网说的其实很明白,这里就简单写一下,文章的最后会提供分布式的Quartz Code Demo,市面上实现Schedule的工具有很多,quartz 只是其中一个,主要的还是要理解其中的思想。

Scheduler的需求是什么,简单来说就是:根据调用方给定的参数和时间来执行某种操作!

一个最简单的思路是,在DB 中维护一张表,里面存储了需要执行的jobdetail,time,status,通过一个windows service来进行检索和调用 ,当达到执行时间的时候,从DB中检索出来需要执行的Job,在执行完成后,更新对应的状态。

 

当然 这其中需要注意的事项有很多很多,这只是实现job 的思路而已,在工作中可以多想想!

 

2..Net Core DI 集成

1.需要安装quartz的NuGet的扩展包

Install-Package Quartz.Extensions.DependencyInjection

2.Startup.ConfigureServices

这个是官网提供的code,而我们在实际开发过程中,根本使用不到这么多配置,但不妨碍了解一下吧。

public void ConfigureServices(IServiceCollection services)
{
    // base configuration from appsettings.json
    services.Configure<QuartzOptions>(Configuration.GetSection("Quartz"));

    // if you are using persistent job store, you might want to alter some options
    services.Configure<QuartzOptions>(options =>
    {
        options.Scheduling.IgnoreDuplicates = true; // default: false
        options.Scheduling.OverWriteExistingData = true; // default: true
    });

    services.AddQuartz(q =>
    {
        // handy when part of cluster or you want to otherwise identify multiple schedulers
        q.SchedulerId = "Scheduler-Core";
        
        // we take this from appsettings.json, just show it's possible
        // q.SchedulerName = "Quartz ASP.NET Core Sample Scheduler";
        
        // as of 3.3.2 this also injects scoped services (like EF DbContext) without problems
        q.UseMicrosoftDependencyInjectionJobFactory();

        // or for scoped service support like EF Core DbContext
        // q.UseMicrosoftDependencyInjectionScopedJobFactory();
        
        // these are the defaults
        q.UseSimpleTypeLoader();
        q.UseInMemoryStore();
        q.UseDefaultThreadPool(tp =>
        {
            tp.MaxConcurrency = 10;
        });

        // quickest way to create a job with single trigger is to use ScheduleJob
        // (requires version 3.2)
        q.ScheduleJob<ExampleJob>(trigger => trigger
            .WithIdentity("Combined Configuration Trigger")
            .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(7)))
            .WithDailyTimeIntervalSchedule(x => x.WithInterval(10, IntervalUnit.Second))
            .WithDescription("my awesome trigger configured for a job with single call")
        );

        // you can also configure individual jobs and triggers with code
        // this allows you to associated multiple triggers with same job
        // (if you want to have different job data map per trigger for example)
        q.AddJob<ExampleJob>(j => j
            .StoreDurably() // we need to store durably if no trigger is associated
            .WithDescription("my awesome job")
        );

        // here's a known job for triggers
        var jobKey = new JobKey("awesome job", "awesome group");
        q.AddJob<ExampleJob>(jobKey, j => j
            .WithDescription("my awesome job")
        );

        q.AddTrigger(t => t
            .WithIdentity("Simple Trigger")    
            .ForJob(jobKey)
            .StartNow()
            .WithSimpleSchedule(x => x.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever())
            .WithDescription("my awesome simple trigger")
        );

        q.AddTrigger(t => t
            .WithIdentity("Cron Trigger")    
            .ForJob(jobKey)
            .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(3)))
            .WithCronSchedule("0/3 * * * * ?")
            .WithDescription("my awesome cron trigger")
        );

        // you can add calendars too (requires version 3.2)
        const string calendarName = "myHolidayCalendar";
        q.AddCalendar<HolidayCalendar>(
            name: calendarName,
            replace: true,
            updateTriggers: true,
            x => x.AddExcludedDate(new DateTime(2020, 5, 15))
        );

        q.AddTrigger(t => t
            .WithIdentity("Daily Trigger")
            .ForJob(jobKey)
            .StartAt(DateBuilder.EvenSecondDate(DateTimeOffset.UtcNow.AddSeconds(5)))
            .WithDailyTimeIntervalSchedule(x => x.WithInterval(10, IntervalUnit.Second))
            .WithDescription("my awesome daily time interval trigger")
            .ModifiedByCalendar(calendarName)
        );
        
        // also add XML configuration and poll it for changes
        q.UseXmlSchedulingConfiguration(x =>
        {
            x.Files = new[] { "~/quartz_jobs.config" };
            x.ScanInterval = TimeSpan.FromSeconds(2);
            x.FailOnFileNotFound = true;
            x.FailOnSchedulingError = true;
        });

        // convert time zones using converter that can handle Windows/Linux differences
        q.UseTimeZoneConverter();
        
        // auto-interrupt long-running job
        q.UseJobAutoInterrupt(options =>
        {
            // this is the default
            options.DefaultMaxRunTime = TimeSpan.FromMinutes(5);
        });
        q.ScheduleJob<SlowJob>(
            triggerConfigurator => triggerConfigurator
                .WithIdentity("slowJobTrigger")
                .StartNow()
                .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()),
            jobConfigurator => jobConfigurator
                .WithIdentity("slowJob")
                .UsingJobData(JobInterruptMonitorPlugin.JobDataMapKeyAutoInterruptable, true)
                // allow only five seconds for this job, overriding default configuration
                .UsingJobData(JobInterruptMonitorPlugin.JobDataMapKeyMaxRunTime, TimeSpan.FromSeconds(5).TotalMilliseconds.ToString(CultureInfo.InvariantCulture)));

        // add some listeners
        q.AddSchedulerListener<SampleSchedulerListener>();
        q.AddJobListener<SampleJobListener>(GroupMatcher<JobKey>.GroupEquals(jobKey.Group));
        q.AddTriggerListener<SampleTriggerListener>();

        // example of persistent job store using JSON serializer as an example
        /*
        q.UsePersistentStore(s =>
        {
            s.UseProperties = true;
            s.RetryInterval = TimeSpan.FromSeconds(15);
            s.UseSqlServer(sqlServer =>
            {
                sqlServer.ConnectionString = "some connection string";
                // this is the default
                sqlServer.TablePrefix = "QRTZ_";
            });
            s.UseJsonSerializer();
            s.UseClustering(c =>
            {
                c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
                c.CheckinInterval = TimeSpan.FromSeconds(10);
            });
        });
        */
    });
	
	// we can use options pattern to support hooking your own configuration
	// because we don't use service registration api, 
	// we need to manually ensure the job is present in DI
	services.AddTransient<ExampleJob>();
				
	services.Configure<SampleOptions>(Configuration.GetSection("Sample"));
	services.AddOptions<QuartzOptions>()
		.Configure<IOptions<SampleOptions>>((options, dep) =>
		{
			if (!string.IsNullOrWhiteSpace(dep.Value.CronSchedule))
			{
				var jobKey = new JobKey("options-custom-job", "custom");
				options.AddJob<ExampleJob>(j => j.WithIdentity(jobKey));
				options.AddTrigger(trigger => trigger
					.WithIdentity("options-custom-trigger", "custom")
					.ForJob(jobKey)
					.WithCronSchedule(dep.Value.CronSchedule));
			}
		});	
		
    // Quartz.Extensions.Hosting allows you to fire background service that handles scheduler lifecycle
    services.AddQuartzHostedService(options =>
    {
        // when shutting down we want jobs to complete gracefully
        options.WaitForJobsToComplete = true;
    });
}

这事我实际应用时添加配置项。

                services.AddSingleton<IJobFactory, QuartzJobFactory>();
                services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>(provider => 
                {
                    var options = new NameValueCollection();
                    options.Set("quartz.jobStore.clustered", 集群属性);
                    options.Set("quartz.threadPool.maxConcurrency", 最多可以同时运行Job数);
                    options.Set("quartz.scheduler.instanceName", 调度程序的name);
                    options.Set("quartz.scheduler.instanceId", 调度程序的ID);
                    options.Set("quartz.scheduler.batchTriggerAcquisitionMaxCount", 最多可以同时触发的job数);
                    options.Set("quartz.jobStore.acquireTriggersWithinLock", 锁定状态下的触发器);
                    options.Set("quartz.serializer.type", "json");
                    options.Set("quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz");
                    options.Set("quartz.jobStore.useProperties", "true");
                    options.Set("quartz.jobStore.driverDelegateType", Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz);
                    options.Set("quartz.jobStore.tablePrefix", "qrtz_");
                    options.Set("quartz.jobStore.dataSource", "myDS");
                    options.Set("quartz.dataSource.myDS.provider", "SqlServer");
                    options.Set("quartz.dataSource.myDS.connectionString", db.ConnectionString);
                    return new StdSchedulerFactory(options);
                });

                services.AddSingleton(provider =>
                {
                    var schedulerFactory = provider.GetService<ISchedulerFactory>();
                    var scheduler = schedulerFactory.GetScheduler().Result;
                    scheduler.JobFactory = provider.GetService<IJobFactory>();
                    FailedJobListener failedJobListener = new FailedJobListener(//RetryIntervalSeconds, //RetryCount);
                    scheduler.ListenerManager.AddJobListener(failedJobListener);
                    SchedulerListener schedulerListener = new SchedulerListener();
                    scheduler.ListenerManager.AddSchedulerListener(schedulerListener);
                    //scheduler.Start();
                    return scheduler;

3.Create  trigger

//执行一次           
return TriggerBuilder .Create() .WithIdentity(triggerId, schedule.JobType)//认证信息 .StartAt(startTime)//StartTime->UTC .WithSimpleSchedule(x => x.WithMisfireHandlingInstructionFireNow()) .Build();

//Cron 表达式,一个task
return TriggerBuilder .Create() .WithIdentity(triggerId, schedule.JobType) .WithCronSchedule(schedule.RunTime)// Cron .Build();

4.Create job Detail

                return JobBuilder
                      .Create(jobType)
                      .RequestRecovery(true)
                      .UsingJobData(JobKey, key)
                      .WithIdentity(schedule.JobName, schedule.JobType)
                      .WithDescription(schedule.JobName)
                      .Build();

5.将Trigger和JobDetail关联起来

   //
        // Summary:
        //     Add the given Quartz.IJobDetail to the Scheduler, and associate the given Quartz.ITrigger
        //     with it.
        //
        // Remarks:
        //     If the given Trigger does not reference any Quartz.IJob, then it will be set
        //     to reference the Job passed with it into this method.
        Task<DateTimeOffset> ScheduleJob(IJobDetail jobDetail, ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));

  

6.执行job

[P  [PersistJobDataAfterExecution]
    [DisallowConcurrentExecution]
    public class RunJobService : IJob
    {

        public async Task Execute(IJobExecutionContext context)
        {
            string jobName = string.Empty;
            try
            {
                //check 
                string content = dataMap.GetString(QuartzConstants.JobContentKey);
                string type = dataMap.GetString(QuartzConstants.JobTypeKey);
                if (!string.IsNullOrEmpty(type))
                {
                        await 执行job的方法;                
                }
                else
                {
                    throw new Exception("JobType is not exist.");
                }
            }
            catch (JobExecutionException ex)
            {
                //logger
                throw ex;
            }
        }
    }

  

 

posted on 2022-04-24 14:39  GwanMin  阅读(771)  评论(0)    收藏  举报