作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。

但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序。

码甲会想到在web程序中做定时任务, 目前有两个方向:

①.AspNetCore自带的HostService, 这是一个轻量级的后台服务, 需要搭配timer完成定时任务

②.老牌Quartz.Net组件,支持复杂灵活的Scheduling、支持ADO/RAM Job任务存储、支持集群、支持监听、支持插件。

此处我们的项目使用稍复杂的Quartz.net实现web定时任务。

项目背景

最近需要做一个计数程序:采用redis计数,设定每小时当日累积数据持久化到关系型数据库sqlite。

       添加Quartz.Net Nuget 依赖包:<PackageReference Include="Quartz" Version="3.0.6" />

①.定义定时任务内容: Job

②.设置触发条件: Trigger

③.将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);
     }
     ]);
}

很多时候,定义的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任务的创建过程。

编码实践

① 定义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[];
            _logger = loggerFactory.CreateLogger<UsageCounterSyncJob>();
        }
         public async Task Execute(IJobExecutionContext context)
        {
            // 触发时间在凌晨,则同步昨天的计数
            var _day = DateTime.Now.ToString("yyyyMMdd");
            )
                _day = DateTime.Now.AddDays(-).ToString("yyyyMMdd");

            await SyncRedisCounter(_day);
            _logger.LogInformation("[UsageCounterSyncJob] Schedule job executed.");
        }
        ......
 }

②注册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;
            }

            ))
                _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)
        {
        }
    }
}

③结合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;同时将【应用程序池】->【正在回收】->不勾选【回收条件】

更多资料请参考:  https://blogs.technet.microsoft.com/erezs_iis_blog/2013/06/26/new-feature-in-iis-8-5-idle-worker-process-page-out/

作者:Julian_酱

感谢您的认真阅读,如有问题请大胆斧正,如果您觉得本文对你有用,不妨右下角点个或加关注。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置注明本文的作者及原文链接,否则保留追究法律责任的权利。

ASP.NET Core2.2+Quartz.Net 实现web定时任务的更多相关文章

  1. ASP.NET Core2基于RabbitMQ对Web前端实现推送功能

    在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...

  2. 深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图

    原文:深度理解IIS下部署ASP.NET Core2.1 Web应用拓扑图 IIS部署ASP.NET Core2.1 应用拓扑图 我们看到相比Asp.Net, 出现了3个新的组件:ASP.NET Co ...

  3. 一步一步带你做WebApi迁移ASP.NET Core2.0

    随着ASP.NET Core 2.0发布之后,原先运行在Windows IIS中的ASP.NET WebApi站点,就可以跨平台运行在Linux中.我们有必要先说一下ASP.NET Core. ASP ...

  4. Asp.net Core2.0 缓存 MemoryCache 和 Redis

    自从使用Asp.net Core2.0 以来,不停摸索,查阅资料,这方面的资料是真的少,因此,在前人的基础上,摸索出了Asp.net Core2.0 缓存 MemoryCache 和 Redis的用法 ...

  5. 从ASP.NET Core2.2到3.0你可能会遇到这些问题

    趁着假期的时间所以想重新学习下微软的官方文档来巩固下基础知识.我们都知道微软目前已经发布了.NET Core3.0的第三个预览版,同时我家里的电脑也安装了vs2019.So,就用vs2019+.NET ...

  6. 【1】Asp.Net Core2.2从环境配置到应用建立

    作者:Eleven 来源:公众号[软谋net] [前言] .Net Core开源&跨平台,已经肉眼可见将成为.Net平台的未来,在企业招聘需求上已经频频见到,接触到很多公司内部都已经开始尝试C ...

  7. 【翻译】asp.net core2.0中的token认证

    原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...

  8. Centos7 编译安装 Nginx Mariadb Asp.net Core2 (实测 笔记 Centos 7.3 + Openssl 1.1.0h + Mariadb 10.3.7 + Nginx 1.14.0 + Asp.net. Core 2 )

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7-x86_64-Minimal-1611.iso 安装步骤: 1.准备 1.0 查看硬 ...

  9. ASP.Net Core2.1中的HttpClientFactory系列一:HttpClient的缺陷

    引言: ASP.NET Core2.1 中出现了一个新的 HttpClientFactory 功能, 它有助于解决开发人员在使用 HttpClient 实例从其应用程序中访问外部 web 资源时可能遇 ...

随机推荐

  1. java类的种类

    1.this this指向当前对象. public class HelloWorld{ String name = "桔子桑"; public void call(){ Syste ...

  2. Python flask中的配置

    当你开始学习Flask时,配置看上去是小菜一碟.你仅仅需要在config.py定义几个变量,然后万事大吉. 然而当你不得不管理一个生产上的应用的配置时,这一切将变得棘手万分. 你不得不设法保护API密 ...

  3. js对象属性值为对象形式取值方式

    console.log(rowData);//取带点的属性值 console.log(rowData['layoutPipegallery.pipegallerycode']);//取带点的属性值

  4. Linnux入门之简介

    一.Linux简介 Minix(教授实验) -> Linux(大三学生Linus)企鹅作为吉祥物 linux主要分为内核版本和发行版本 linux 内核版本 :官网下载:https://www. ...

  5. UML类图10分钟快速入门 - From 圣杰

    虚线箭头指向依赖: 实线箭头指向关联: 虚线三角指向接口: 实线三角指向父类: 空心菱形能分离而独立存在,是聚合: 实心菱形精密关联不可分,是组合: 原文作者:圣杰 原文地址:http://www.j ...

  6. 安装SQL Server DQS 和 MDS

    tep1:   安装特性时选择Data Quality Services 和 Master Data Services Step2:  安装完成之后, 打开 SQL Server 2017 Data ...

  7. 网站开发中使用javascript获取浏览器滚动条宽度

    在网站开发中,有时候需要获取浏览器滚动条的宽度,在武汉蚂蹄软件服务中心的技术人员指导之下,我实现了该需求.记录如下: 首先说明一下原理: ①生成一个div,设置滚动条不可见,记录其宽度: ②将上面的d ...

  8. 解决MyEclipse注册失败的问题

    https://jingyan.baidu.com/article/acf728fd49519ff8e410a361.html

  9. linux内核裁剪及编译可加载模块

    一:linux内核裁剪: 1:编译内核源码: 今天的重点内容是内核驱动的编写,在编写驱动之前首先的了解linux内核源码,linux主要是由五个子系统组成:进程调度,内存管理,文件系统,网络接口以及进 ...

  10. SSM-SpringMVC-21:SpringMVC中处理器方法之返回值Object篇

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 今天要记录的是处理方法,返回值为Object的那种,我给它分了一下类: 1.返回值为Object数值(例如1) ...