在上一篇  Quartz.net 开源job调度框架(一) 中讲到了基本的使用以及配置job轮训数据执行

这种做法适用于对数据操作实时性要求不高的场景,在实际场景中还有一种比较常用的场景就是我们需要在某一个时间点立即执行某个操作,比如商城做抢购活动,同时开启多个活动在不同的时间点开始促销。如果我们采用轮训数据库的方式来实现的话会出现处理数据不及时的情况,因为每次都需要从数据库捞取一批次的数据,根据状态或者设定的活动开启时间循环比对,如果达到时间点就更新数据状态,开启活动,每一批次处理的数据都需要时间,很容易就会在某一个活动已经到达开启的时间点,但是job执行不及时导致活动的开启时间晚于设定的时间点,误差根据数据量以及内部逻辑的复杂度会递增。这样就会导致某一个活动在设定的开启时间点没有准时开启,如果是商城做抢购倒计时活动的话,这中延迟对客户来说是不被接受的。下面是我最近做的H5 商城的实例,这是一个抢购活动的列表页,多个活动在不同时间点开启或结束。

这是进行中的活动:

这是就绪状态,等待开启的活动:

我们想要在活动设定的某一个时间点准时开启,就需要使用Quartz 中的另外一种方式来配置Job 在固定时间点执行。

在次之前我们还要考虑的一个问题就是抢购的活动是通过后台添加的,随时都有可能增加,所以我们不仅仅是只从数据库捞一次活动的数据,而是需要定时轮训数据库找出需要执行的活动,根据后台设定的开启或者结束时间,添加到Quartz的调度队列,让它在固定时间点自己执行。

看到这里大家可能就要问开头我们就说到不采用轮训的方式来做,为什么这里又要说轮训。注意了,我开始提到的是不轮询每一个活动,在满足开启条件(状态,开启/结束时间)的情况下再开启。而这里说到的轮询指的是轮询有没有新添加进来的活动,这是完全不一样的概念。

闲话不多说,上代码。先按照前一篇中讲到的轮询方式新建一个MonitorJob:

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
44
45
46
47
48
49
50
51
52
53
54
namespace JobSchedule.JobMonitorSchedule
{
    public class JobMonitorJob : IJob
    {
        NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
        public void Execute(IJobExecutionContext context)
        {
            log.Info("监控Job开启执行------------");
            var processDataList = FlashItemOfflineDBHelper.GetOfflineFlashPromotion();
            if (processDataList != null && processDataList.Count > 0)
            {
                processDataList.ForEach(data =>
                {
                    if (data.Status == 2)
                    {
                        if (!ScheduleBase.Scheduler.CheckExists(JobKey.Create("上线商品作业:" + data.SysNo, "定时触发作业组" + +data.SysNo)))
                        {
                            var job = JobBuilder.Create(typeof(ItemOnlineJob))
                                           .WithIdentity("上线商品作业:" + data.SysNo, "定时触发作业组" + +data.SysNo)
                                           .UsingJobData("ItemSysNo", data.SysNo)
                                           .Build();
                            var trigger = TriggerBuilder.Create()
                                                .WithIdentity("上线商品作业Trigger" + data.SysNo, "作业触发器" + data.SysNo)
                                                .StartAt(data.PromotionStartTime.AddSeconds(ConstValue.ItemOnlineStartOffset))
                                                .Build();
                            ScheduleBase.Scheduler.ScheduleJob(job, trigger);
                            log.Info(string.Format("监控Job开启执行,商品上线作业已加入调度池, 活动编号:{0},活动名称:{1}, 活动开始时间:{2}", data.SysNo, data.PromotionName, data.PromotionStartTime));
                        }
                    }
                    if (data.Status == 3)
                    {
                        if (!ScheduleBase.Scheduler.CheckExists(JobKey.Create("下线商品作业:" + data.SysNo, "定时触发作业组" + +data.SysNo)))
                        {
                            var job = JobBuilder.Create(typeof(ItemOfflineJob))
                                           .WithIdentity("下线商品作业:" + data.SysNo, "定时触发作业组" + +data.SysNo)
                                           .UsingJobData("ItemSysNo", data.SysNo)
                                           .Build();
                            var trigger = TriggerBuilder.Create()
                                                .WithIdentity("下线商品作业Trigger:" + data.SysNo, "作业触发器" + data.SysNo)
                                                .StartAt(data.PromotionEndTime.AddSeconds(ConstValue.ItemOfflineStartOffset))
                                                .Build();
                            ScheduleBase.Scheduler.ScheduleJob(job, trigger);
                            log.Info(string.Format("监控Job开启执行,商品下线作业已加入调度池, 活动编号:{0},活动名称:{1}, 活动结束时间:{2}", data.SysNo, data.PromotionName, data.PromotionEndTime));
                        }
                    }
                });
            }
        }
    }
}

根据每一个活动的状态来判断是需要加入到开启队列的,还是加入到结束队列的(2:就绪状态的活动,即将要开启;3:已经开启的活动,即将要结束)

我们可以看到创建一个作业需要两个条件,第一创建你要执行的实例,第二告诉Quartz你想要在什么时候执行。可以看到我们用到了UsingJobData的方法,这是Quartz中提供的内部方法,用于给加入到执行队列中的作业传递数据用的,有6次重载,可以传递下面几种类型的数据:

1
2
3
4
5
6
7
8
9
10
11
public JobBuilder UsingJobData(string key, string value);
public JobBuilder UsingJobData(string key, int value);
public JobBuilder UsingJobData(string key, long value);
public JobBuilder UsingJobData(string key, float value);
public JobBuilder UsingJobData(string key, double value);
public JobBuilder UsingJobData(string key, bool value);

在这里我传递的是活动编号。

创建完MonitorJob之后还是按照上一篇文章讲的方式加入到调度器:

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
public partial class JobManager : ServiceBase
    {
        public JobManager()
        {
            InitializeComponent();
        }
        protected override void OnStart(string[] args)
        {
            //开启调度器
            ScheduleBase.Scheduler.Start();
            //把作业,触发器加入调度器
            ScheduleBase.AddSchedule(new AutoVoidUnPaidFlashOrderService());
            ScheduleBase.AddSchedule(new AutoVoidUnPaidNormalOrderService());
            ScheduleBase.AddSchedule(new JobMonitorService());
        }
        protected override void OnStop()
        {
            ScheduleBase.Scheduler.Shutdown(true);
        }
    }

这样基本算是完成了,接下来就是具体的实现类了,需要注意的是我们在使用 ScheduleBase.Scheduler.ScheduleJob(job, trigger) 创建作业的时候Job名称不能重复,所以在上面我们是根据活动Id来创建的。

接下来看实现类 ItemOnlineJob(活动上线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
public class ItemOnlineJob : IJob
   {
       NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
       public void Execute(IJobExecutionContext context)
       {
           log.Info("促销活动上线Job开启执行------------");
           try
           {
               var sysno = context.JobDetail.JobDataMap.GetIntValue("ItemSysNo");
               log.Info(string.Format("促销活动上线Job:上线处理开始,促销活动编号:{0}", sysno));
               if (sysno > 0)
               {
                   var promotion = FlashItemOfflineDBHelper.GetOfflineFlashPromotionBySysNo(sysno);
                   //就绪的活动并且已经到达开启时间自动开启
                   if (promotion != null && promotion.Status == (int)FlashSaleStatusType.BeReady)
                   {
                       log.Info("促销活动上线Job:上线处理请求开始,促销活动编号:" + sysno);
                       FlashItemOfflineDBHelper.UpdatePromotionStatus(sysno, (int)FlashSaleStatusType.Processing);
                       log.Info("抢购商品到期上线Job:活动已开启,活动编号:" + promotion.SysNo);
                   }
               }
           }
           catch (Exception ex)
           {
               log.Error("促销活动上线Job执行异常:" + ex.Message);
           }
       }
   }

可以看 context.JobDetail.JobDataMap 中存储的就是我们在创建作业的时候传的数据,在Job实时执行的时候可以取出来。

context.JobDetail.JobDataMap中提供了对应的几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public virtual double GetDoubleValue(string key);
public virtual double GetDoubleValueFromString(string key);
public virtual float GetFloatValue(string key);
public virtual float GetFloatValueFromString(string key);
public virtual int GetIntValue(string key);
public virtual int GetIntValueFromString(string key);
public virtual long GetLongValue(string key);
public virtual long GetLongValueFromString(string key);

ItemOfflineJob用于控制活动结束下架,实现和上线一样。

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
namespace JobSchedule.JobMonitorSchedule
{
    public class ItemOfflineJob : IJob
    {
        NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
        public void Execute(IJobExecutionContext context)
        {
            log.Info("促销活动下线Job开启执行------------");
            try
            {
                var sysno = context.JobDetail.JobDataMap.GetIntValue("ItemSysNo");
                log.Info(string.Format("促销活动下线Job:下线处理开始,促销活动编号:{0}", sysno));
                if (sysno > 0)
                {
                    var promotion = FlashItemOfflineDBHelper.GetOfflineFlashPromotionBySysNo(sysno); 
                    if (promotion != null && promotion.Status == (int)FlashSaleStatusType.Processing)
                    {
                        log.Info("促销活动下线Job:下线处理请求开始,促销活动编号:" + sysno);
                        FlashItemOfflineDBHelper.UpdatePromotionOffline(sysno, (int)FlashSaleStatusType.Finished);
                        log.Info("抢购商品到期下线Job:活动已开启,活动编号:" + promotion.SysNo);
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("促销活动下线Job执行异常:" + ex.Message);
            }
        }
    }
}

代码实现完了,我们来看看Web界面上的呈现如下:

顺便再总结一下本次项目中遇到的几个坑:

1.活动界面倒计时

最开始的时候计算倒计时的时候偷懒了,从客户端取了时间来做倒计时,导致界面上显示的倒计时不准确,这个只能取服务端的时间。实在是不应该犯的低级错误。

2.倒计时时间乱跳的问题,场景是我有两个倒计时的活动,从活动列表页面先后进入到详情页面的时候两个计时器都在跑,导致倒计时的时间一直在闪动

最后分析原因是我的倒计时是在每一次进入到详情页面的时候开启的,先后有两个活动的时候就会触发两个定时器,这时界面上的显示就是两个倒计时同时切换,导致时间闪动

试想想如果有3个或者更多个,界面时间直接就看不清了。最后的做法是在每一次进入到详情界面的时候把界面上所有的定时器清空,然后重新生成,这样就解决了。

http://www.cnblogs.com/Wolfmanlq/p/5918864.html

Quartz.net 开源job调度框架(二)----定点执行相关推荐

  1. Quartz.net 开源job调度框架(一)

    Quartz.NET是一个开源的作业调度框架,非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等. Quartz.NET允许开发人员根据时间间隔(或天)来调度作业.它实现了作业和 ...

  2. Quartz.Net 调度框架配置介绍

    在平时的工作中,估计大多数都做过轮询调度的任务,比如定时轮询数据库同步,定时邮件通知等等.大家通过windows计划任务,windows服务等都实现过此类任务,甚至实现过自己的配置定制化的框架.那今天 ...

  3. 调度框架学习笔记(3)—— 集群调度框架的架构演进过程

    本章是 The evolution of cluster scheduler architectures 文章的学习笔记.这篇文章讨论了这些年调度架构是如何发展的以及为什么会这样发展. 首先介绍一下这 ...

  4. 集群调度框架的架构演进过程

    原文:The evolution of cluster scheduler architectures 作者:Malte Schwarzkopf 之前组会上,有幸与大家探讨 Firmament: Fa ...

  5. 开源调度框架xxl-job集成SpringBatch详解

    文章目录 一.启动xxl-job调度中心 二.配置部署执行器项目 1.maven依赖 2.执行器配置 3.执行器组件配置 4.springbatch调度任务开发 5.调度中心,新建执行器 6.调度中心 ...

  6. 阿里P8架构师谈:Quartz调度框架详解、运用场景、与集群部署实践

    以下将分别从Quartz架构简介.集群部署实践.Quartz监控.集群原理分析详解Quartz任务调度框架. Quartz简介 Quartz是Java领域最著名的开源任务调度工具,是一个任务调度框架, ...

  7. java 调度框架_java调度框架Quartz(一)

    前段时间项目中使用到了quartz这个调度框架,最近有时间正好可以做一总结,现在使用的主要是两个版本,一个就是2.0以下版本,还要一个就是2.0以上今天咱们从quartz的本地启动-->quar ...

  8. struts框架的原理和应用_分布式开源调度框架TBSchedule原理与应用

    主要内容: 第一部分 TBSchedule基本概念及原理 1. 概念介绍 2. 工作原理 3. 源码分析 4. 与其他开源调度框架对比 第二部分 TBSchedule分布式调度示例 1. TBSche ...

  9. 开源游戏服务器框架NoahGameFrame(NF)服务器端环境搭建(二)

    一.下载NoahGameFrame 1.进入到开源游戏服务器框架NoahGameFrame在GitHub的官方界面NoahGameFrame 2.复制要Checkout的资源目录URL 3.在任意一个 ...

最新文章

  1. python tkinter库、添加gui界面_使用Python中tkinter库简单gui界面制作及打包成exe的操作方法(二)...
  2. iOS黑科技之(CoreImage)静态人脸识别(一)
  3. javascript 错误与调试
  4. python中 普通方法_python中普通方法classmethod和staticmethod的区别与用法
  5. The executable was signed with invalid entitlements
  6. LUA面向对象编程技巧
  7. 几种人类设计的永动机,最后一个彻底服了!| 今日最佳
  8. 领域应用 | OMAHA联盟发布“疾病临床表现”、“中毒”知识图谱及OMAHA知识库
  9. java vector_Java Vector sureCapacity()方法与示例
  10. 性能提升120倍!滴滴东北大学提出自动结构化剪枝压缩算法框架
  11. 2020年最前沿的 8 本AI技术图书—文末留言赠8本
  12. 二叉树的创建、前序遍历、中序遍历、后序遍历
  13. 随想录(用好自己的时间)
  14. 读书笔记——《程序员的思维修炼:开发认知潜能的九堂课》
  15. 【PHP实现微信公众平台开发—基础篇】第2章 微信公众账号及申请流程详解
  16. 64位驱动 hp630打印机_HP LaserJet1010 打印机驱动win7 64位
  17. Android注册时总是出现验证码不正确问题的解决
  18. 系统学习机器学习之cox模型
  19. 【Java分享客栈】SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。
  20. 一起读Apache ServiceComb

热门文章

  1. Qt Creator在桌面上预览
  2. C++求一个整数的各位数字总和(附完整源码)
  3. QT的QTechnique类的使用
  4. QT的QStatusBar类的使用
  5. QT的QLibrary类的使用
  6. QT的QJSEngine类的使用
  7. C语言中的一维数组和二维数组
  8. 模拟使用Flume监听日志变化,并且把增量的日志文件写入到hdfs中
  9. 2cocos2dx别踩白块游戏案例
  10. 通过已有SQL语句,生成数据库模型PDM