微信扫一扫

028-83195727 , 15928970361
business@forhy.com

.NET作业调度 Quartz.NET

2016-08-11

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">CrystalQuartz 管理面板</a>
4.启动自己的定时任务

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



六、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();
 
        }


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 ()方法重新调度。仅仅重新初始化在先前的应用程序运行时终止的作业,不会正确地装载触发器的属性。


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