在上一篇 Quartz.net 開源job調度框架(一) 中講到了基本的使用以及配置job輪訓數據執行html
這種作法適用於對數據操做實時性要求不高的場景,在實際場景中還有一種比較經常使用的場景就是咱們須要在某一個時間點當即執行某個操做,好比商城作搶購活動,同時開啓多個活動在不一樣的時間點開始促銷。若是咱們採用輪訓數據庫的方式來實現的話會出現處理數據不及時的狀況,由於每次都須要從數據庫撈取一批次的數據,根據狀態或者設定的活動開啓時間循環比對,若是達到時間點就更新數據狀態,開啓活動,每一批次處理的數據都須要時間,很容易就會在某一個活動已經到達開啓的時間點,可是job執行不及時致使活動的開啓時間晚於設定的時間點,偏差根據數據量以及內部邏輯的複雜度會遞增。這樣就會致使某一個活動在設定的開啓時間點沒有準時開啓,若是是商城作搶購倒計時活動的話,這中延遲對客戶來講是不被接受的。下面是我最近作的H5 商城的實例,這是一個搶購活動的列表頁,多個活動在不一樣時間點開啓或結束。數據庫
這是進行中的活動:微信
這是就緒狀態,等待開啓的活動:微信公衆平臺
咱們想要在活動設定的某一個時間點準時開啓,就須要使用Quartz 中的另一種方式來配置Job 在固定時間點執行。框架
在次以前咱們還要考慮的一個問題就是搶購的活動是經過後臺添加的,隨時都有可能增長,因此咱們不只僅是隻從數據庫撈一次活動的數據,而是須要定時輪訓數據庫找出須要執行的活動,根據後臺設定的開啓或者結束時間,添加到Quartz的調度隊列,讓它在固定時間點本身執行。ide
看到這裏你們可能就要問開頭咱們就說到不採用輪訓的方式來作,爲何這裏又要說輪訓。注意了,我開始提到的是不輪詢每個活動,在知足開啓條件(狀態,開啓/結束時間)的狀況下再開啓。而這裏說到的輪詢指的是輪詢有沒有新添加進來的活動,這是徹底不同的概念。學習
閒話很少說,上代碼。先按照前一篇中講到的輪詢方式新建一個MonitorJob:ui
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:已經開啓的活動,即將要結束)spa
咱們能夠看到建立一個做業須要兩個條件,第一建立你要執行的實例,第二告訴Quartz你想要在何時執行。能夠看到咱們用到了UsingJobData的方法,這是Quartz中提供的內部方法,用於給加入到執行隊列中的做業傳遞數據用的,有6次重載,能夠傳遞下面幾種類型的數據:.net
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個或者更多個,界面時間直接就看不清了。最後的作法是在每一次進入到詳情界面的時候把界面上全部的定時器清空,而後從新生成,這樣就解決了。
之前沒有作過移動端的開發,本次算是踩着坑過來了,也學習了很多,總結一下,繼續前行。
歡迎關注微信公衆平臺:上帝派來改造世界的人