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

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

这是进行中的活动:

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

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

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

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

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

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次重载,可以传递下面几种类型的数据:

        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之后还是按照上一篇文章讲的方式加入到调度器:

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):

 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中提供了对应的几个方法:

        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用于控制活动结束下架,实现和上线一样。

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个或者更多个,界面时间直接就看不清了。最后的做法是在每一次进入到详情界面的时候把界面上所有的定时器清空,然后重新生成,这样就解决了。

以前没有做过移动端的开发,本次算是踩着坑过来了,也学习了不少,总结一下,继续前行。

欢迎关注微信公众平台:上帝派来改造世界的人

Quartz.net 开源job调度框架(二)----定点执行的更多相关文章

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

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

  2. Quartz.Net任务统一调度框架

    山寨版Quartz.Net任务统一调度框架   TaskScheduler 在日常工作中,大家都会经常遇到Win服务,在我工作的这些年中一直在使用Quartz.Net这个任务统一调度框架,也非常好用, ...

  3. 山寨版Quartz.Net任务统一调度框架

    TaskScheduler 在日常工作中,大家都会经常遇到Win服务,在我工作的这些年中一直在使用Quartz.Net这个任务统一调度框架,也非常好用,配置简单,但是如果多个项目组的多个服务部署到一台 ...

  4. 开源调度框架Quartz最佳实践

    开源调度框架Quartz最佳实践 Quartz是一个Java调度框架,当前的最新版本为2.2.1. 以Quartz 2.2.1版为例,Quartz最佳实践(用于生产系统)总结如下: 1.跳过更新检查Q ...

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

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

  6. 分布式开源调度框架TBSchedule原理与应用

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

  7. Quartz.net(调度框架) 使用Mysql作为存储

    最近公司的做的项目中涉及到配置任务地址然后按照配置去目标地址提取相关的数据,所以今天上午在Internet上查看有关定时任务(调度任务)的相关信息,筛选半天然后查找到Quartz.net. Quart ...

  8. Quartz.NET开源作业调度框架系列

    Quartz.NET是一个被广泛使用的开源作业调度框架 , 由于是用C#语言创建,可方便的用于winform和asp.net应用程序中.Quartz.NET提供了巨大的灵活性但又兼具简单性.开发人员可 ...

  9. Quartz.NET---任务调度框架

    在我们的程序中,可能经常会遇到"每隔多久执行XXX任务"这样的问题:每天晚上24:00审核用户提交的申请:每隔1分钟去数据库中检索用户是否有新的消息:...   ...那么Quar ...

随机推荐

  1. 再谈CAAnimation动画

    CAAnimaton动画分为CABasicAnimation & CAKeyframeAnimation CABasicAnimation动画, 顾名思义就是最基本的动画, 老规矩先上代码: ...

  2. 用html5的canvas和JavaScript创建一个绘图程序

    本文将引导你使用canvas和JavaScript创建一个简单的绘图程序. 创建canvas元素 首先准备容器Canvas元素,接下来所有的事情都会在JavaScript里面. <canvas ...

  3. FILE文件流的中fopen、fread、fseek、fclose的使用

    FILE文件流用于对文件的快速操作,主要的操作函数有fopen.fseek.fread.fclose,在对文件结构比较清楚时使用这几个函数会比较快捷的得到文件中具体位置的数据,提取对我们有用的信息,满 ...

  4. ie6 ie7 ie8 ie9兼容问题终极解决方案

    放下包袱,解决低版本兼容问题   这是一个老生常谈的问题,自然解决这个问题的方案也比较多,下面整理了一些解决方法: 1.强制使用高版本渲染模式. 强制使用Edge模式来解析网页代码 <meta ...

  5. Android中BroadcastReceiver的两种注册方式(静态和动态)详解

    今天我们一起来探讨下安卓中BroadcastReceiver组件以及详细分析下它的两种注册方式. BroadcastReceiver也就是"广播接收者"的意思,顾名思义,它就是用来 ...

  6. React Native 之 Text的使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  7. Ajax.BeginForm方法 参数

    感谢博主 http://www.cnblogs.com/zzgblog/p/5454019.html toyoung 在Asp.Net的MVC中的语法,在Razor页面中使用,替代JQuery的Aja ...

  8. js分页页码算法

    function get_hs_page(cur_page, total_page) { var result = ""; ; i <= total_page; i++) { ...

  9. Mono 3.8发布:性能进一步改进,可伸缩性提升

    9月4日,Mono 3.8.0发布了.该版本的运行时带来了一些性能和可伸缩性方面的改进,同时完成了向Windows平台的移植. Mono遵循Gnome和Linux内核的版本编号策略,这意味着3.8是3 ...

  10. centos6.X使用Apache+Mono搭建asp.net 环境

    mark 一下时间  2016年1月19日09:42:49 mono是指由Novell公司(由Xamarin发起,并由Miguel de lcaza领导的,一个致力于开创·NET在Linux上使用的开 ...