博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Quartz.net 开源job调度框架(二)----定点执行
阅读量:5369 次
发布时间:2019-06-15

本文共 6914 字,大约阅读时间需要 23 分钟。

在上一篇   中讲到了基本的使用以及配置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

转载于:https://www.cnblogs.com/soundcode/p/7988258.html

你可能感兴趣的文章
mysql的limit经典用法及优化
查看>>
C#后台程序与HTML页面中JS方法互调
查看>>
mysql 同一个表中 字段a 的值赋值到字段b
查看>>
linux系统可执行文件添加环境变量使其跨终端和目录执行
查看>>
antiSMASH数据库:微生物次生代谢物合成基因组簇查询和预测
查看>>
UNICODE与ANSI的区别
查看>>
nginx 配置实例
查看>>
Flutter - 创建底部导航栏
查看>>
ASP.NET MVC 教程-MVC简介
查看>>
SQL Server索引 - 聚集索引、非聚集索引、非聚集唯一索引 <第八篇>
查看>>
转载:详解SAP TPM解决方案在快速消费品行业中的应用
查看>>
Android OpenGL ES 开发(N): OpenGL ES 2.0 机型兼容问题整理
查看>>
项目中用到的技术及工具汇总(持续更新)
查看>>
【算法】各种排序算法测试代码
查看>>
HDU 5776 Sum
查看>>
201521123044 《Java程序设计》第9周学习总结
查看>>
winfrom 图片等比例压缩
查看>>
人工智能实验报告一
查看>>
用LR12录制app,用LR11跑场景,无并发数限制,已试验过,可行!
查看>>
python 多线程就这么简单(转)
查看>>