• Net作业调度


    Net作业调度(一) -Quartz.Net入门

    2014-11-01 13:14 by 蘑菇先生, 13954 阅读, 7 评论, 收藏编辑

    背景

    很多时候,项目需要在不同时刻,执行一个或很多个不同的作业。

    Windows执行计划这时并不能很好的满足需求了,迫切需要一个更为强大,方便管理,集群部署的作业调度框架。

    介绍

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

    它一些很好的特性:

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

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

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

    实战

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

       Scheduler     作业调度器。

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

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

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

    Nuget安装 

     PM> Install-Package Quartz 

    下面是简单使用例子,附带详细的注释:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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();         //关闭调度器。
           }

    声明要执行的作业,HelloJob:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /// <summary>
       /// 作业
       /// </summary>
       public class HelloJob : IJob
       {
           public void Execute(IJobExecutionContext context)
           {
               Console.WriteLine("作业执行!");
           }
       }

    声明要执行的作业,DumbJob:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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);
            }
        }

    其WithCronSchedule("") 拥有强大的Cron时间表达式,正常情况下WithSimpleSchedule(x) 已经满足大部分对日期设置的要求了。

    Quartz.Net官方2.X教程  http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/index.html

    Quartz.Net开源地址   https://github.com/quartznet/quartznet

    Net作业调度(二) -CrystalQuartz远程管理

    2014-11-01 18:21 by 蘑菇先生, 6245 阅读, 24 评论, 收藏编辑

    Source Code-1.6M

     介绍

    上篇已经了解Quartz.NET的基本使用方法了。但如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个Job管理的界面。

    本文介绍Quartz.NET如何进行远程job管理,如图:

    实战

    一:作业服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    static void Main(string[] args)
           {
               var properties = new NameValueCollection();
               properties["quartz.scheduler.instanceName"] = "RemoteServerSchedulerClient";
     
               // 设置线程池
               properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
               properties["quartz.threadPool.threadCount"] = "5";
               properties["quartz.threadPool.threadPriority"] = "Normal";
     
               // 远程输出配置
               properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";
               properties["quartz.scheduler.exporter.port"] = "556";
               properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";
               properties["quartz.scheduler.exporter.channelType"] = "tcp";
     
               var schedulerFactory = new StdSchedulerFactory(properties);
               var scheduler = schedulerFactory.GetScheduler();
     
               var job = JobBuilder.Create<PrintMessageJob>()
                   .WithIdentity("myJob""group1")
                   .Build();
     
               var trigger = TriggerBuilder.Create()
                   .WithIdentity("myJobTrigger""group1")
                   .StartNow()
                   .WithCronSchedule("/10 * * ? * *")
                   .Build();
               scheduler.ScheduleJob(job, trigger);
               scheduler.Start();
     
           }
    1
    2
    3
    4
    5
    6
    7
    public class PrintMessageJob : IJob
       {
           public void Execute(IJobExecutionContext context)
           {
               Console.WriteLine("Hello!");
           }
       }

    启动如下

    二:作业远程管理端,无需写任何代码,引用官方程序集,嵌入到已有的web网站。 

          PM> Install-Package CrystalQuartz.Remote

          Webconfig 需要配置的地方

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <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>

     Web管理界面

    其他

    CrystalQuartz 提供基础功能,可以继续在此基础上进行二次开发,另外推荐使用Window服务寄宿,比较方法。

    参考资源

    张善友               http://www.cnblogs.com/shanyou/archive/2012/01/15/2323011.html

    CrystalQuartz开源的地址   https://github.com/guryanovev/CrystalQuartz

    Net作业调度(三) — Quartz.Net进阶

    介绍

    前面介绍Quartz.Net的基本用法,但在实际应用中,往往有更多的特性需求,比如记录job执行的执行历史,发邮件等。

    阅读目录

    1. Quartz.Net插件
    2. TriggerListener,JobListener
    3. Cron表达式
    4. Quartz.Net线程池
    5. 总结

    Quartz.Net插件

         Quartz.net 自身提供了一个插件接口(ISchedulerPlugin)用来增加附加功能,看下官方定义:

    1
    2
    3
    4
    5
    6
    7
    8
    public interface ISchedulerPlugin
      {
          void Initialize(string pluginName, IScheduler sched);
         //关闭调度器
          void Shutdown();
          //插件启动
          void Start();
      }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class MyPlugin : ISchedulerPlugin
       {
           public void  Initialize(string pluginName, IScheduler sched)
           {
               Console.WriteLine("实例化");
           }
           public  void Start()
           {
                Console.WriteLine("启动");
           }
           public  void Shutdown()
           {
               Console.WriteLine("关闭");
           }
       }

      主函数里面配置要实现的插件,注释部分,一句话搞定。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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();
     
            }

    运行结果如下:

     

    TriggerListener,JobListener

    这2个是对触发器和job本身的行为监听器,这样更好方便跟踪Job的状态及运行情况。  

    ITriggerListener是官方定义的接口,这里我们直接继承实现。 

    复制代码
    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; } }
        }
    复制代码

    主函数添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //添加监听器到指定的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同理,这里不多做描述。

    Cron表达式

    quartz中的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触发。

    Quartz.Net线程池

    线程池数量设置:

    properties["quartz.threadPool.threadCount"] = "5";  

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

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

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

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

    配置如下:

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

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

    总结

    官方有LoggingTriggerHistoryPlugin,LoggingJobHistoryPlugin  已实现的,触发器和job历史记录的插件。

    Quartz.Plugin 命名空间下有官方实现的其他一些插件,也可以自己增加扩展。

    quartz中监听器还有SchedulerListener,使用方法基本一样。 

    本文基于自用经验和官方文档代码来写的,部分是直接翻译的。 

    Quartz.Net官方教程http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/index.html

    Net作业调度(四)—quartz.net持久化和集群

    2015-01-18 15:17 by 蘑菇先生, 7462 阅读, 16 评论, 收藏编辑

    介绍

    在实际使用quartz.net中,持久化能保证实例重启后job不丢失、 集群能均衡服务器压力和解决单点问题。

    quartz.net在这两方面配置都比较简单。

    持久化

    quartz.net的持久化,是把job、trigger一些信息存储到数据库里面,以解决内存存储重启丢失。

    下载sql脚本

               https://github.com/quartznet/quartznet/blob/master/database/tables/tables_sqlServer.sql

    创建个数据库,并执行脚本

      QRTZ_BLOB_TRIGGERS  以Blob 类型存储的触发器。

      QRTZ_CALENDARS   存放日历信息, quartz.net可以指定一个日历时间范围。

      QRTZ_CRON_TRIGGERS  cron表达式触发器。

      QRTZ_JOB_DETAILS      job详细信息。

      QRTZ_LOCKS       集群实现同步机制的行锁表

      QRTZ_SCHEDULER_STATE   实例信息,集群下多使用。

     quartz.net 配置

    复制代码
      //===持久化====
                //存储类型
                properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
                //表明前缀
                properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
                //驱动类型
                properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
                //数据源名称
                properties["quartz.jobStore.dataSource"] = "myDS";
                //连接字符串
                properties["quartz.dataSource.myDS.connectionString"] = @"Data Source=(local);Initial Catalog=JobScheduler;User ID=sa;Password=123465";
                //sqlserver版本
                properties["quartz.dataSource.myDS.provider"] = "SqlServer-20";
    复制代码

    启动客户端

    复制代码
      var properties = JobsManager.GetProperties();
                var schedulerFactory = new StdSchedulerFactory(properties);
                scheduler = schedulerFactory.GetScheduler();
                scheduler.Start();
    
                //var job = JobBuilder.Create<MonitorJob>()
                //    .WithIdentity("test", "value")
                //    .Build();
    
                //var trigger = (ICronTrigger) TriggerBuilder.Create()
                //    .WithIdentity("test", "value")
                //    .WithCronSchedule("0 0/5 * * * ?")
                //    .Build();
                //scheduler.ScheduleJob(job, trigger);
    复制代码

    补充

         1: 持久化后,job只有添加一次了(数据库已经有了),所以不能再执行端写添加job的行为。这时候需要一个管理工具,动态添加操作。

         2: quartz.net 支持sql server、sqlite、mysql、oracle、mongodb(非官方版)。

    集群

    部署图:

     

    如图quartz.net 的集群模式是依赖数据库表的,所以要持久化配置。  集群节点之间是不通信的,这样分布式的架构,很方便进行水平扩展。

    1: 除了线程池数量,instanceId可以不同外,各个节点的配置必须是一样的。

    2:集群中节点的系统时间一致。  

    3:多线程、集群中。quartz.net 利用数据库锁来保证job不会重复执行。

         源码在DBSemaphore.cs、UpdateLockRowSemaphore.cs、StdRowLockSemaphore.cs

    4:集群化后,某节点失效后,剩余的节点能保证job继续执行下去。

    实例配置后启动。

       //cluster
                properties["quartz.jobStore.clustered"] = "true";
                properties["quartz.scheduler.instanceId"] = "AUTO";

    简单管理界面:

    Net作业调度(五)—quartz.net动态添加job设计

    2015-01-19 08:42 by 蘑菇先生, 8343 阅读, 35 评论, 收藏编辑

    介绍

    在实际项目使用中quartz.net中,都希望有一个管理界面可以动态添加job,而避免每次都要上线发布。 

    也看到有园子的同学问过。这里就介绍下实现动态添加job的几种方式, 也是二次开发的核心模块。

    阅读目录:

    1. 传统方式
    2. 框架反射方式
    3. 进程方式
    4. URL方式
    5. 框架配置方式

    传统方式

     继承IJob,实现业务逻辑,添加到scheduler。

    复制代码
    public class MonitorJob : IJob
        {
            public void Execute(IJobExecutionContext context)
            {
                //do something
                Console.WriteLine("test");
            }
        }
     //var job = JobBuilder.Create<MonitorJob>()
                //    .WithIdentity("test", "value")
                //    .Build();
                //var trigger = (ICronTrigger) TriggerBuilder.Create()
                //    .WithIdentity("test", "value")
                //    .WithCronSchedule("0 0/5 * * * ?")
                //    .Build();
                //scheduler.ScheduleJob(job, trigger);
    复制代码

    也可以使用CrystalQuartz远程管理暂停取消。之前的博客CrystalQuartz远程管理(二)

    框架反射方式

    这种方式需要定义一套接口框架。 比如:

    复制代码
      interface IcustomJob
        {
            void Excute(string context);
            void Failed(string error);
            void Complete(string msg);
        }
    复制代码

    1:当我们写job时同一实现这个框架接口,类库形式。

    2:写完后编译成DLL,上传到我们的作业执行节点。

    3:在执行节点中,通过反射拿到DLL的job信息。

    4:然后构建quartz的job,添加到scheduler。

    这种方式缺点: 耦合性太高,开发量较大。 优点:集中式管理。

    系统结构如图:

    进程方式

    这个方式和windows任务计划类似。

    1:使用方编写自己的job,无需实现任何接口,可执行应用程序形式。

    2:将程序发送到执行节点,由执行节点起进程调用job程序。

    执行节点调用,示例如下:

    复制代码
     public class ConsoleJob:IJob
        {
            public void Execute(IJobExecutionContext context)
            {
                JobDataMap dataMap = context.JobDetail.JobDataMap;
                string content = dataMap.GetString("jobData");
                var jd = new JavaScriptSerializer().Deserialize<ConsoleJobData>(content);
    
                Process p = new Process();
                p.StartInfo.UseShellExecute = true;
                p.StartInfo.FileName = jd.Path;
                p.StartInfo.Arguments = jd.Parameters;   //空格分割
                p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
                p.Start();
            }
        }
    复制代码

    这种方式相对来说: 耦合性中等,执行节点和job相互不关心,没有依赖,开发量较小。

    系统结构如图:

    URL方式

    URL方式和第三种类似,不过调用的不在是执行程序,而是URL。

    1: 使用方在网页或服务中,实现业务逻辑。

    2: 然后将Url,交给执行节点post或get执行。

    执行节点调用,示例如下:

    复制代码
     public class HttpJob : IJob
        {
            public void Execute(IJobExecutionContext context)
            {
                var dataMap = context.JobDetail.JobDataMap;
    
                var content = dataMap.GetString("jobData");
    
                var jd = new JavaScriptSerializer().Deserialize<HttpJobData>(content);
    
                if (jd.Parameters == null)
                    jd.Parameters = string.Empty;
                if (jd.Timeout == 0)
                    jd.Timeout = 5*60;
    
                var result = RequestHelper.Post(jd.Url, jd.ContentType, jd.Timeout, jd.Parameters, jd.heads);
            }
        }
    复制代码

    这种方式耦合比较低,使用方不需要单独写应用程序了,和平常业务开发一样。

    执行节点的职权,仅仅作为一个触发器。

    有2点需要注意的是:

    1:请求URL时,注意双方约定token加密,防止非执行节点执行调用。

    2:使用方,如果有耗时操作,建议异步执行。 

    系统结构如图:

    框架配置方式

    1:使用方直接使用quartz.net框架,实现自己的job。从管理方拉取执行节点配置,然后自行管理执行节点。

    2:使用方也可以暴露端口给管理方,以实现监控,修改配置。

    这种形式,耦合性最低。是把管理方当成一个配置中心。     ps:几乎和传统方式+CrystalQuartz一样了。

    通过context.JobDetail.JobDataMap,可以保存job的需要的信息。

    本篇介绍主流的几种实现方案,供大家参考使用。

  • 相关阅读:
    【转】双口RAM
    Beep使用
    fcntl函数
    ioctl() 参数
    线程属性:pthread_attr_t
    GPIO
    Linux CGI编程基础
    看门狗watchdog
    Linux库知识大全
    linux进程间通讯
  • 原文地址:https://www.cnblogs.com/weber4444/p/6478043.html
Copyright © 2020-2023  润新知