在上一篇  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. B样条基函数的定义和性质

    定义:令U={u0,u1,…,um}是一个单调不减的实数序列,即ui≤ui+1,i=0,1,…,m-1.其中,ui称为节点,U称为节点矢量,用Ni,p(u)表示第i个p次(p+1阶)B样条基函数,其定 ...

  2. javascript 判断参数类型大全

    js 判断类型的在开发中是很常用的,因为js 是弱类型的语言,var 可以接受任何形式的类型,但是在真正的开发中,我们需要根据不同类型做不同的处理,所以这个是必须的精通. 首先需要知道 typeof这 ...

  3. Http请求

    HTTP报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的.HTTP有两类报文:请求报文和响应报文. 请求报文 一个HTTP请求报文由请求行(request line ...

  4. PHP代码优化

    1 代码优化 1 尽量静态化 如果一个方法能被静态,那就声明它为静态的,速度可提高1/4,甚至我测试的时候,这个提高了近三倍. 当然了,这个测试方法需要在十万级以上次执行,效果才明显. 其实静态方法和 ...

  5. java设计模式之单例模式(几种写法及比较)

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

  6. Jqprint实现页面打印

    好些项目需要实现页面打印,特别是一些后台管理类系统,下面介绍一款轻量级的打印插件: 1.实现页面打印要引入jQuery和Jqprint.点击下载Jqprint插件 <script languag ...

  7. 【干货分享】流程DEMO-外出申请

    流程名: 外出申请  流程相关文件: 流程包.xml  流程说明: 直接导入流程包文件,即可使用本流程  表单:   流程: 图片:2.png DEMO包下载: http://files.cnblog ...

  8. 一个简单的网站web项目的详解

    有不对的术语,或者不好理解的部分,欢迎大家批评指正,谢谢大家! 近期做的网站web项目,实现登录功能,查询功能.首先把这个项目分为几个模块来处理,当前用户模块,历史用户模块,历史记录模块,数据库模块, ...

  9. 在配有英特尔® Iris™ 显卡的系统上通过优化对 Just Cause 3 进行增强

    高端 PC 继续通过高性能显卡驱动桌面游戏. 一流的"梦想机器"基于第六代智能 英特尔® 酷睿™ 处理器i7-6700K等 CPU,通常与高端独立显卡配合使用以运行要求最严苛的游戏 ...

  10. 分享阿里云推荐码 IC1L2A,购买服务器可以直接打9折,另附阿里云服务器部署ASP.NET MVC5关键教程

    阿里云推荐码为:IC1L2A 阿里云还是不错滴. 以windows server 2008 R2为例,介绍如何从全新的服务器部署MVC5 站点. 新购买的阿里云服务器是没有IIS的,要安装IIS: 控 ...