天天看点

.NET作业调度 Quartz.NET

Quartz是一个开源的作业调度框架,OpenSymphony的开源项目。Quartz.Net 是Quartz的C#移植版本。

一.特性:

1:支持集群,作业分组,作业远程管理。 

2:自定义精细的时间触发器,使用简单,作业和触发分离。

3:数据库支持,可以寄宿Windows服务,WebSite,winform等。

二、基本概念:

Quartz框架的一些基础概念解释:

   Scheduler     作业调度器。

   IJob             作业接口,继承并实现Execute, 编写执行的具体作业逻辑。

  JobBuilder       根据设置,生成一个详细作业信息(JobDetail)。

  TriggerBuilder   根据规则,生产对应的Trigger

三、dll:

Quartz.dll

Common.Logging.dll

四、简单例子-基本使用 :

static void Main(string[] args)
       {
           //从工厂中获取一个调度器实例化
           IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
 
           scheduler.Start();       //开启调度器
 
           //==========例子1(简单使用)===========
 
           IJobDetail job1 = JobBuilder.Create<HelloJob>()  //创建一个作业
               .WithIdentity("作业名称", "作业组")
               .Build();
 
           ITrigger trigger1 = TriggerBuilder.Create()
                                       .WithIdentity("触发器名称", "触发器组")
                                       .StartNow()                        //现在开始
                                       .WithSimpleSchedule(x => x         //触发时间,5秒一次。
                                           .WithIntervalInSeconds(5)
                                           .RepeatForever())              //不间断重复执行
                                       .Build();
 
 
           scheduler.ScheduleJob(job1, trigger1);      //把作业,触发器加入调度器。
 
           //==========例子2 (执行时 作业数据传递,时间表达式使用)===========
 
           IJobDetail job2= JobBuilder.Create<DumbJob>()
                                       .WithIdentity("myJob", "group1")
                                       .UsingJobData("jobSays", "Hello World!")
                                       .Build();
 
 
           ITrigger trigger2 = TriggerBuilder.Create()
                                       .WithIdentity("mytrigger", "group1")
                                       .StartNow()
                                       .WithCronSchedule("/5 * * ? * *")    //时间表达式,5秒一次     
                                       .Build();
 
 
           scheduler.ScheduleJob(job2, trigger2);     
         
           //scheduler.Shutdown();         //关闭调度器。
       }
           

声明要执行的作业,实现IJob接口的execute方法:

/// <summary>
   /// 作业
   /// </summary>
   public class HelloJob : IJob
   {
       public void Execute(IJobExecutionContext context)
       {
           Console.WriteLine("作业执行!");
       }
   }
           
public class DumbJob : IJob
    {
        /// <summary>
        ///  context 可以获取当前Job的各种状态。
        /// </summary>
        /// <param name="context"></param>
        public void Execute(IJobExecutionContext context)
        {
 
            JobDataMap dataMap = context.JobDetail.JobDataMap;
 
            string content = dataMap.GetString("jobSays");
 
            Console.WriteLine("作业执行,jobSays:" + content);
        }
    } 
           

五、Quartz.NET 的CrystalQuartz远程管理:

如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个Job管理的界面,作业远程管理端,无需写任何代码,引用官方程序集,嵌入到已有的web网站。

1.相关程序集:

CrystalQuartz.Core.dll

CrystalQuartz.Web.dll

Common.Logging.dll

NVelocity.dll

Quartz.dll

RemoteSchedulerManager.dll

2.webconfig 配置:

<configuration> 
    <crystalQuartz>
        <provider>
            <add property="Type" value="CrystalQuartz.Core.SchedulerProviders.RemoteSchedulerProvider, CrystalQuartz.Core" />
            <add property="SchedulerHost" value="tcp://127.0.0.1:556/QuartzScheduler" /> <!--TCP监听的地址-->
        </provider>
 
    </crystalQuartz>
<system.webServer>
      <!-- Handler拦截处理了,输出作业监控页面-->
        <handlers>
            <add name="CrystalQuartzPanel" verb="*" path="CrystalQuartzPanel.axd" type="CrystalQuartz.Web.PagesHandler, CrystalQuartz.Web" />
        </handlers>
    </system.webServer>
</configuration>
           

3.html页面链接设置:

<a style="font-size: 2em;" href="/CrystalQuartzPanel.axd" target="_blank" rel="external nofollow" >CrystalQuartz 管理面板</a>
           

4.启动自己的定时任务

5.点击链接即可进入管理界面,并看到自己的作业:

.NET作业调度 Quartz.NET

六、Quartz.NET 进阶:

1.Quartz.NET插件-ISchedulerPlugin:

    在实际应用中,往往有更多的特性需求,比如记录job执行的执行历史,发邮件等。Quartz.net 自身提供了一个插件接口(ISchedulerPlugin)用来增加附加功能,看下官方定义

public interface ISchedulerPlugin
  {
      void Initialize(string pluginName, IScheduler sched);
     //关闭调度器
      void Shutdown();
      //插件启动
      void Start();
  }
           

继承接口,实现自己的插件:

public class MyPlugin : ISchedulerPlugin
   {
       public void  Initialize(string pluginName, IScheduler sched)
       {
           Console.WriteLine("实例化");
       }
       public  void Start()
       {
            Console.WriteLine("启动");
       }
       public  void Shutdown()
       {
           Console.WriteLine("关闭");
       }
   }
           

主函数里面配置要实现的插件:

static void Main(string[] args)
        {
            var properties = new NameValueCollection();
           //MyPlugin 自定义名称。    "命名空间.类名,程序名称"
            properties["quartz.plugin.MyPlugin.type"] = "QuartzDemo3.MyPlugin,QuartzDemo3";
 
            var schedulerFactory = new StdSchedulerFactory(properties);
            var scheduler = schedulerFactory.GetScheduler();
 
            var job = JobBuilder.Create<HelloJob>()
                .WithIdentity("myJob", "group1")
                .Build();
 
            var trigger = TriggerBuilder.Create()
                                .WithIdentity("mytrigger", "group1")
                                .WithCronSchedule("/2 * * ? * *")
                                .Build();
 
            scheduler.ScheduleJob(job, trigger);
            scheduler.Start();
            Thread.Sleep(6000);
            scheduler.Shutdown(true);
            Console.ReadLine();
 
        }
           
.NET作业调度 Quartz.NET

2.TriggerListener 和JobListener:

    这2个是对触发器和job本身的行为监听器,这样更好方便跟踪Job的状态及运行情况。  通过实现ITriggerListener或IJobListener接口来实现自己的监听器:

public class MyTriggerListener : ITriggerListener
    {
        private string name;

        public void TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode)
        {
            Console.WriteLine("job完成时调用");
        }
        public void TriggerFired(ITrigger trigger, IJobExecutionContext context)
        {
            Console.WriteLine("job执行时调用");
        }
        public void TriggerMisfired(ITrigger trigger)
        {
            Console.WriteLine("错过触发时调用(例:线程不够用的情况下)");
        }
        public bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context)
        {
            //Trigger触发后,job执行时调用本方法。true即否决,job后面不执行。
            return false;
        }
        public string Name { get { return name; } set { name = value; } }
    }
           

主函数添加:

//添加监听器到指定的trigger
 scheduler.ListenerManager.AddTriggerListener(myJobListener, KeyMatcher<TriggerKey>.KeyEquals(new TriggerKey("mytrigger", "group1")));
 
 添加监听器到指定分类的所有监听器。
 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"));
 
 添加监听器到指定分类的所有监听器。
 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"));
 
添加监听器到指定的2个分组。
 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"), GroupMatcher<TriggerKey>.GroupEquals("myJobGroup2"));
 
 添加监听器到所有的触发器上。
 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.AnyGroup());
 
 scheduler.Start();
           

JobListener同理。

3.Cron表达式:

quartz.NET中的cron表达式和Linux下的很类似,比如 "/5 * * ? * * *"  这样的7位表达式,最后一位年非必选。

表达式从左到右,依此是秒、分、时、月第几天、月、周几、年。下面表格是要遵守的规范:

字段名 允许的值 允许的特殊字符
Seconds 0-59 , - * /
Minutes 0-59 , - * /
Hours 0-23 , - * /
Day of month 1-31 , - * ? / L W
Month 1-12 or JAN-DEC , - * /
Day of week 1-7 or SUN-SAT , - * ? / L #
Year 空, 1970-2099 , - * /
特殊字符 解释
, 或的意思。例:分钟位 5,10  即第5分钟或10分都触发。 
/ a/b。 a:代表起始时间,b频率时间。 例; 分钟位  3/5,  从第三分钟开始,每5分钟执行一次。
* 频率。 即每一次波动。    例;分钟位 *  即表示每分钟 
- 区间。  例: 分钟位   5-10 即5到10分期间。 
? 任意值 。   即每一次波动。只能用在DayofMonth和DayofWeek,二者冲突。指定一个另一个一个要用?
L 表示最后。 只能用在DayofMonth和DayofWeek,4L即最后一个星期三
W 工作日。  表示最后。 只能用在DayofWeek
# 4#2。 只能用DayofMonth。 某月的第二个星期三  

实例介绍

”0 0 10,14,16 * * ?"    每天10点,14点,16点 触发。

"0 0/5 14,18 * * ?"    每天14点或18点中,每5分钟触发 。

"0 4/15 14-18 * * ?"       每天14点到18点期间,  从第四分钟触发,每15分钟一次。

"0 15 10 ? * 6L"        每月的最后一个星期五上午10:15触发。

4.Quartz.NET线程池:

线程池数量设置:

properties["quartz.threadPool.threadCount"] = "5";//是指同一时间,调度器能执行Job的最大数量。
           

这个线程池的设置,是指同时间,调度器能执行Job的最大数量。

quartz是用每个线程跑一个job。上面的设置可以解释是job并发时能执行5个job,剩下的job如果触发时间恰好到了,当前job会进入暂停状态,直到有可用的线程。

如果在指定的时间范围依旧没有可用线程,会触发misfired时间。

quartz 提供了IThreadPool接口,也可以用自定义线程池来实现。

配置如下:

properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";       

一般来说作业调度很少并发触发大量job,如果有上百个JOB,可在服务器承受范围内适量增加线程数量

七、Quartz.NET持久化-JobStore:

    作业一旦被调度,调度器需要记住并且跟踪作业和它们的执行次数。如果你的作业是30分钟后或每30秒调用,这不是很有用。事实上,作业执行需要非常

准确和即时调用在被调度作业上的Execute()方法。Quartz.NET通过一个称之为作业存储(JobStore)的概念来做作业存储和管理。

    Quartz.NET提供两种基本作业存储类型。第一种类型叫做RAMJobStore,它利用通常的内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行。Quartz.net缺省使用的就是RAMJobStore。对许多应用来说,这种作业存储已经足够了。

    然而,因为调度程序信息是存储在被分配在内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。如果你需要在重新启动之间持久化调度信息,则将需要第二种类型的作业存储。为了修正这个问题,Quartz.NET 提供了 AdoJobStore。顾名思义,作业仓库通过

ADO.NET把所有数据放在数据库中。数据持久性的代价就是性能降低和复杂性的提高。它将所有的数据通过ADO.NET保存到数据库可中。它的配置要比RAMJobStore稍微复杂,同时速度也没有那么快。但是性能的缺陷不是非常差,尤其是如果你在数据库表的主键上建立索引。

    AdoJobStore几乎可以在任何数据库上工作,它广泛地使用Oracle, MySQL, MS SQLServer2000,HSQLDB, PostreSQL 以及 DB2。要使用AdoJobStore,首先必须创建一套Quartz使用的数据库表,可以在Quartz的database\tables找到创建库表的SQL脚本。如果没有找到你的

数据库类型的脚本,那么找到一个已有的,修改成为你数据库所需要的。需要注意的一件事情就是所有Quartz库表名都以QRTZ_作为前缀(例如:表"QRTZ_TRIGGERS",及"QRTZ_JOB_DETAIL")。实际上,可以你可以将前缀设置为任何你想要的前缀,只要你告诉AdoJobStore

那个前缀是什么即可(在你的Quartz属性文件中配置)。对于一个数据库中使用多个scheduler实例,那么配置不同的前缀可以创建多套库表,十分有用。(下载SQL脚本)。

    配置:

properties["quartz.scheduler.instanceName"] = "TestScheduler";
            properties["quartz.scheduler.instanceId"] = "instance_one";
            properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
            properties["quartz.threadPool.threadCount"] = "5";
            properties["quartz.threadPool.threadPriority"] = "Normal";
            properties["quartz.jobStore.misfireThreshold"] = "60000";
            properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
            properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz";
            properties["quartz.jobStore.useProperties"] = "false";
            properties["quartz.jobStore.dataSource"] = "default";
            properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
            properties["quartz.jobStore.clustered"] = "true";
            // if running MS SQL Server we need this
            properties["quartz.jobStore.selectWithLockSQL"] = "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = @lockName";

            properties["quartz.dataSource.default.connectionString"] = @"Server=V-LOZHU02;Database=quartz;Trusted_Connection=True;";
            properties["quartz.dataSource.default.provider"] = "SqlServer-20";
           

    持久化后,job只有添加一次了(数据库已经有了),所以不能再执行端写添加job的行为,可以在执行job前先清空数据库中的job防止重复添加报异常:

public virtual void CleanUp(IScheduler inScheduler)
        {
            _log.Warn("***** Deleting existing jobs/triggers *****");

            // unschedule jobs
            string[] groups = inScheduler.TriggerGroupNames;
            for (int i = 0; i < groups.Length; i++)
            {
                String[] names = inScheduler.GetTriggerNames(groups[i]);
                for (int j = 0; j < names.Length; j++)
                    inScheduler.UnscheduleJob(names[j], groups[i]);
            }

            // delete jobs
            groups = inScheduler.JobGroupNames;
            for (int i = 0; i < groups.Length; i++)
            {
                String[] names = inScheduler.GetJobNames(groups[i]);
                for (int j = 0; j < names.Length; j++)
                    inScheduler.DeleteJob(names[j], groups[i]);
            }
        }
           

先从初始化 SchedulerFactory 和Scheduler开始。然后,不再需要初始化作业和触发器,而是要获取触发器群组名称列表,之后对于每个群组名称,获取触发器名称列表。请注意,每个现有的作业都应当用

Scheduler. RescheduleJob ()方法重新调度。仅仅重新初始化在先前的应用程序运行时终止的作业,不会正确地装载触发器的属性。

8. 将Quartz.NET 服务创建到window services中:https://github.com/zhulongxi2015/Quartz.NETProject/tree/master

8.1.首先创建Windows服务程序(windows service):QuartzArchitecture.Service

引用程序集:Quartz.dll, log4net.dll,Common.Logging.Log4Net.dll(不能少,没有它的话在创建服务的时候回有1053错误码),Common.Logging.dll

此时自动生成一个ProjectInstaller.cs服务组件类,将serviceInstaller1的StartType设为Automatic,serviceProcessInstaller1的Account设置为LocalSystem.

新建一个自己的服务组件类:MyQuartzService1.cs,继承自ServiceBase类,在里面分别重写OnStart,OnStop,OnPause,OnContinue方法。在构造函数中实例化scheduler..

在app.confg中配置log4net ,common, quartz节点:

<configSections>
    <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>

    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
    </sectionGroup>

  </configSections>
           
<common>
    <logging>
      <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">
        <arg key="configType" value="INLINE"/>
        <arg key="level" value="ALL" />
        <arg key="showLogName" value="true" />
        <arg key="showDataTime" value="true" />
        <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
      </factoryAdapter>
    </logging>
  </common>

  <log4net>
    <appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="log/" />
      <appendToFile value="true" />
      <param name="DatePattern" value="yyyyMMdd".txt"" />
      <rollingStyle value="Date" />
      <maxSizeRollBackups value="100" />
      <maximumFileSize value="1024KB" />
      <staticLogFileName value="false" />
      <Encoding value="UTF-8" />
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="INFO" />
        <param name="LevelMax" value="INFO" />
      </filter>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %-5level %logger  - %message%newline" />
      </layout>
    </appender>
    <appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="log/error.txt" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="100" />
      <maximumFileSize value="10240KB" />
      <staticLogFileName value="true" />
      <Encoding value="UTF-8" />
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="WARN" />
        <param name="LevelMax" value="FATAL" />
      </filter>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %-5level %logger - %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="INFO" />
      <appender-ref ref="InfoFileAppender" />
      <appender-ref ref="ErrorFileAppender" />
    </root>
  </log4net>

  <quartz>
    <add key="quartz.scheduler.instanceName" value="ServerScheduler" />

    <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
    <add key="quartz.threadPool.threadCount" value="10" />
    <add key="quartz.threadPool.threadPriority" value="2" />

    <add key="quartz.jobStore.misfireThreshold" value="60000" />
    <add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
  </quartz>
           

8.2.创建Job类库:MyQuartzArchitecture.Jobs

在里面添加自己的job:(实现IJob接口)

8.3.创建Runner类库,表示使用哪种存储(adostore\simple):QuartzRunner

8.4 编译服务QuartzRunner,将bin中的文件拷到磁盘下面,(如d:\service),

通过cmd: sc create servicename binpath=d:\svervice\QuartzArchitecture.Service.exe 创建服务。

net start servicename 启用服务

net stop servicename 停止服务

source code: https://github.com/zhulongxi2015/Quartz.NETSource

demo: https://github.com/zhulongxi2015/Quart.NETSimpleDemo

http://www.cnblogs.com/lzrabbit/archive/2012/04/14/2446942.html