• ASP.NET Core托管运行Quartz.NET作业调度详解


    Quartz.NET这么NB的作业调度系统,不会还行?

    今天介绍一下Quartz.NET的托管运行,官网传送门

    一、前言

    Quartz.NET,按官网上的说法,是一款功能齐全的任务调度系统,从小型应用到大型企业级系统都能适用。在众多项目中,Quartz.NET以可靠、集群的方式,被用作在定时器上运行后台任务的一种方式。

    Quartz.NET主要完成两个方面的内容:

    1. 基于时间计划的后台作业;
    2. 基于因时间计划的触发的任务运行。

    ASP.NET Core本身对于通过托管服务运行后台任务就支持的很好。当ASP.NET启动托管服务时,应用程序启动,并在生命周期内在后台运行。通过创建Quartz.NET托管服务,可以使用标准的.Net Core托管服务,在后台运行任务。

    Quartz.NET可以创建定时的任务,例如每十分钟运行一个任务。除此之外,Quartz.NET还可以通过Cron触发器,定义任务在特定的日子或特定的时间运行,例如每天凌晨两点执行一个任务。它还允许以集群的方式运行应用程序的多个实例,以便在任何时间确保只有一个实例运行给定的任务。

    下面,就针对这些特性和功能,进行详细的说明。

        为防止非授权转发,这儿给出本文的原文链接:https://www.cnblogs.com/tiger-wang/p/13861121.html

    二、安装Quartz.NET

    Quartz.NET提供了NuGet包,所以安装很简单:

    % dotnet add package quartz

    这是个司机就知道,不详说了。

    看一下安装后的.csproj文件内容:

    <Project Sdk="Microsoft.NET.Sdk.Web">
      <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Include="quartz" Version="3.2.2" />
      </ItemGroup>
    </Project>

    三、通过IJob创建任务类

    我们用个例子来说明 - 创建一个Demo的实现。它将写入ILogger<>。我们会使用Quartz.NET的接口IJob来实现,并使用依赖注入将日志注入到构造函数中。

    [DisallowConcurrentExecution]
    public class DemoJob : IJob
    {
        private readonly ILogger<DemoJob> _logger;
        public DemoJob(ILogger<DemoJob> logger)
        
    {
            _logger = logger;
        }

        public Task Execute(IJobExecutionContext context)
        
    {
            _logger.LogInformation("Demo !");
            return Task.CompletedTask;
        }
    }

    在类的前面,我用了一个DisallowConcurrentExecution属性。这个属性可以防止Quartz.NET同时运行相同的作业。

    四、通过IJobFactory创建任务工厂

    通常情况下,Quartz.NET会使用Activator.CreateInstance来创建作业的实例。

    在我们这个例子里,我们换一种方式。使用IJobFactory实现,并与ASP.NET Core的依赖注入容器挂钩。

    public class SingletonJobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;
        public SingletonJobFactory(IServiceProvider serviceProvider)
        
    {
            _serviceProvider = serviceProvider;
        }

        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        
    {
            return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
        }

        public void ReturnJob(IJob job)
        
    {
        }
    }

    这个IJobFactory的实现,在构造函数中引入IServiceProvider,并实现接口。

    接口中,最重要的是NewJob()方法。这个方法需要返回Quartz.NET调度器请求的IJob。在我们的例子中,我们直接委托给IServiceProvider,并让DI容器找到所需的实例。

    ReturnJob()方法是调度程序返回和销毁IJobFactory创建的作业的地方。不过,因为我们使用了IServiceProvider,而它没有提供这样的处理。所以,从安全的角度,应该使用单例作业。

    五、配置作业

    在第三节中,我们创建了一个IJob的实现。这个实现直接使用就可以。

    但是,我们这儿要加点内容。我们把Quartz的托管服务做成一个通用实现,来调度任意的作业。因此,我们创建一个简单的DTO,并使用它来定义一个给定作业类型的时间器调度:

    public class JobSchedule
    {

        public JobSchedule(Type jobType, string cronExpression)
        
    {
            JobType = jobType;
            CronExpression = cronExpression;
        }

        public Type JobType { get; }
        public string CronExpression { get; }
    }

    在这个类中,JobType是一个作业的类型,例如本例子中的DemoJobCronExpression是一个Cron的表达式。

    Cron表达式允许复杂的计时器调度,所以可以设置规则,比如每个月的5号和20号,在早上8点到10点之间每半小时触发一次。

    关于Quartz.NETCron表达式,可以在这儿查到。

    注意:不同操作系统使用的Cron表达式有一定的区别,写表达式的时候一定要注意这一点。

    然后,我们把作业添加到Startup.ConfigureServices()中:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddSingleton<IJobFactory, SingletonJobFactory>();
        services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();

        services.AddSingleton<DemoJob>();
        services.AddSingleton(new JobSchedule(
            jobType: typeof(DemoJob),
            cronExpression: "0/5 * * * * ?")); // 每5秒运行一次
    }

    这段代码向DI添加了四个单例对象:

    1. SingletonJobFactory,第四节的类,用于创建作业实例;
    2. ISchedulerFactory的一个实现,是内置的StdSchedulerFactory,用于处理调度和管理作业;
    3. DemoJob作业本身;
    4. DemoJob的一个JobSchedule实例,该实例具有每5秒运行一次的Cron表达式。

    现在,主要代码已经完成,就差Quartz托管服务了。

    六、创建Quartz托管服务

    QuartzHostedService是自己创建的Quartz托管服务,继承于IHostedService,用于设置Quartz调度器,并在后台启动它。

    先看一下完整的代码:

    public class QuartzHostedService : IHostedService
    {
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;
        private readonly IEnumerable<JobSchedule> _jobSchedules;

        public QuartzHostedService(ISchedulerFactory schedulerFactory, IJobFactory jobFactory, IEnumerable<JobSchedule> jobSchedules)
        
    {
            _schedulerFactory = schedulerFactory;
            _jobSchedules = jobSchedules;
            _jobFactory = jobFactory;
        }

        public IScheduler Scheduler { get; set; }

        public async Task StartAsync(CancellationToken cancellationToken)
        
    {
            Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            Scheduler.JobFactory = _jobFactory; 

            foreach (var jobSchedule in _jobSchedules)
            {
                var job = CreateJob(jobSchedule);
                var trigger = CreateTrigger(jobSchedule);

                await Scheduler.ScheduleJob(job, trigger, cancellationToken);
            }

            await Scheduler.Start(cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        
    {
            await Scheduler?.Shutdown(cancellationToken);
        }

        private ITrigger CreateTrigger(JobSchedule schedule)
        
    {
            return TriggerBuilder
            .Create()
            .WithIdentity($"{schedule.JobType.FullName}.trigger")
            .WithCronSchedule(schedule.CronExpression)
            .WithDescription(schedule.CronExpression)
            .Build();
        }

        private IJobDetail CreateJob(JobSchedule schedule)
        
    {
            var jobType = schedule.JobType;
            return JobBuilder
                .Create(jobType)
                .WithIdentity(jobType.FullName)
                .WithDescription(jobType.Name)
                .Build();
        }
    }

    解释一下这段代码:

    这段代码中,QuartzHostedService有三个依赖项:Startup.ConfigureServices()中注入的ISchedulerFactoryIJobFactory,以及一个IEnumerable。在第五节的代码中,我们只向DI添加了一个JobSchedule,就是DemoJob。我们也可以添加多个JobSchedule,他们都会在这个IEnumerable中被注入到托管服务中。

    StartAsync在应用程序启动时被调用,它是我们配置Quartz的地方。我们首先创建IScheduler的一个实例,为它分配一个属性供以后使用,并将调度程序的JobFactory设置为注入的实例:

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
        Scheduler.JobFactory = _jobFactory; 
        //... 
    }

    然后,循环注入的作业调度,并在类的最后使用CreateJobCreateTrigger方法为每个作业创建一个IJobDetailITrigger。实际应用中如果有别的需要,也可以通过扩展JobSchedule DTO来定制它。

    最后,在调度了所有作业之后,调用Scheduler.Start()来实际在后台启动Quartz.NET调度器。当应用程序关闭时,框架将调用StopAsync(),此时可以调用Scheduler.Shutdown()来安全地关闭调度程序进程。

    全部完成后,我们启动QuartzHostedService

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddHostedService<QuartzHostedService>();
    }

    运行程序,可以看到结果:

    demo.DemoJob: Information: Demo !
    info: demo.DemoJob[0]
          Demo !
    demo.DemoJob: Information: Demo !
    info: demo.DemoJob[0]
          Demo !
    demo.DemoJob: Information: Demo !
    info: demo.DemoJob[0]
          Demo !
    demo.DemoJob: Information: Demo !
    info: demo.DemoJob[0]
          Demo !

    本文的代码,在https://github.com/humornif/Demo-Code/tree/master/0029/demo


    微信公众号:老王Plus

    扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

    本文版权归作者所有,转载请保留此声明和原文链接

  • 相关阅读:
    [转]Java 反射在实际开发中的应用
    phantomjs生成网页快照,有些https 证书不支持问题解决
    PHP + TrackingMore物流信息跟踪
    rabbitMQ消息队列 – Message方法解析
    MySQL UNION 与 UNION ALL 语法与用法
    提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
    php商品对比功能代码分享
    Redis类的源码使用
    php+redis实现消息队列
    PHP中利用redis实现消息队列处理高并发请求
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/13861121.html
Copyright © 2020-2023  润新知