第十节: 利用SQLServer实现Quartz的持久化和双机热备的集群模式
背景:?默認情況下,Quartz.Net作業是持久化在內存中的,即 quartz.jobStore.type = "Quartz.Simpl.RAMJobStore, Quartz",這種模式有以下弊端:
① 想在A服務器上控制B服務器上已經發布了的job和trigger不方便;
② 很難實現Web端(寄宿在IIS上)管理作業,客戶端(發布成服務)的這種模式。
③ 最大弊端就是一旦服務器宕機或者重啟,調度器Schdeuler對應的所有作業(job、trigger及其對應關系)將丟失,不得不重新發布;
?解決方案:
針對問題1. 可以借助Remote代理的模式,通過TCP協議在A服務器上直接獲取B服務器上的Scheduler,然后進行操作。(詳情點擊)
針對問題2. 客戶端作為Server端進行調度的執行,Web端通過Remote模式獲取客戶端的中的Scheduler,然后進行作業的管理,問題是一旦客戶端端掛機,Web端是連接不上的。
針對問題3. 無論代理還是不代理,只要Server端一掛機,保存在內存中的作業都會丟失,所以這個時候,我們需要另辟蹊徑,將作業持久化進行遷移,比如遷移到數據庫中,這樣話,即使服務器宕機,數據庫中存儲的作業信息仍然存在,下次只需要開啟Scheduler即可,無須配置job和trigger了,同時也解決了上述問題1和問題2,即都可以直接修改數據庫即可。
該章節也是為開篇提出的目標三鋪最后一道路,下面著重介紹持久化SQLServer數據庫。
步驟1:準備數據庫腳本。
下載地址為:https://github.com/quartznet/quartznet/blob/master/database/tables/tables_sqlServer.sql,執行后的數據庫如下圖:
重點介紹一下以上表的含義:
qrtz_blob_triggers?: 以Blob?類型存儲的觸發器。?
qrtz_calendars:存放日歷信息, quartz可配置一個日歷來指定一個時間范圍。?
qrtz_cron_triggers:存放cron類型的觸發器。?
qrtz_fired_triggers:存放已觸發的觸發器。?
qrtz_job_details:存放一個jobDetail信息。?
qrtz_job_listeners:job**監聽器**。?
qrtz_locks: 存儲程序的悲觀鎖的信息(假如使用了悲觀鎖)。?
qrtz_paused_trigger_graps:存放暫停掉的觸發器。?
qrtz_scheduler_state:調度器狀態。?
qrtz_simple_triggers:存放簡單觸發器的信息。?
qrtz_trigger_listeners:觸發器監聽器。?
qrtz_triggers:將Trigger和job進行關聯的表。
注:cron方式需要用到的4張數據表:?qrtz_cron_triggers,qrtz_fired_triggers,qrtz_job_details,qrtz_triggers。
步驟2:代碼進行持久化數據庫配置
需要配置的信息有SQLServer版本、數據庫連接字符串、存儲類型、數據源名稱、驅動類型,代碼如下:
1 var properties = new NameValueCollection();2 //SQLServer版本3 properties.Add("quartz.dataSource.myDS.provider", "SqlServer-20");4 //表名前綴(可有可無)5 //properties.Add("quartz.jobStore.tablePrefix", "QRTZ_");6 //數據庫連接字符串7 properties.Add("quartz.dataSource.myDS.connectionString", "Data Source=.;Initial Catalog=quartz;User ID=sa;Password=123456");8 //properties.Add("quartz.dataSource.myDS.connectionString", "Server =.;Database = quartz;Trusted_Connection =True;"); 9 //JobStore設置(JobStoreTX: 帶有事務;JobStoreCMT:不帶有事務) 10 //存儲類型 11 properties.Add("quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"); 12 //數據源名稱 13 properties.Add("quartz.jobStore.dataSource", "myDS"); 14 //驅動類型 15 properties.Add("quartz.jobStore.driverDelegateType", "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz");步驟3:向數據庫中持久化作業,并開啟調度。
代碼如下:
1 var factory = new StdSchedulerFactory(properties);2 IScheduler scheduler = factory.GetScheduler();3 var job = JobBuilder.Create<HelloJob4>()4 .WithIdentity("ypfJob1", "ypfJobGroup1")5 .Build();6 var trigger = TriggerBuilder.Create()7 .WithIdentity("ypfTrigger1", "ypfTriggerGroup1")8 .WithCronSchedule("/3 * * * * ?")9 .Build(); 10 if (!scheduler.CheckExists(job.Key)) 11 { 12 scheduler.ScheduleJob(job, trigger); 13 } 14 scheduler.Start();運行結果為:
?
此時分析數據庫中的數據:
QRTZ_CRON_TRIGGERS??表:即存放cron類型的trigger
?QRTZ_JOB_DETAILS? 表:即存放job的信息
?
QRTZ_TRIGGERS?表:將Trigger和job進行關聯的表
?QRTZ_FIRED_TRIGGERS?表:
?
下面做幾個實驗,驗證持久化問題:
?實驗1:去掉代碼中job和trigger的創建及關聯,直接進行調度器的啟動。
?
實驗結果:調度正常按照每3s執行一次,證明作業持久化數據庫成功。
?
實驗2:修改數據庫中的cron表達式為每5s執行一次,然后保持實驗1中的代碼注釋,運行代碼。
?
實驗結果:調度變為每隔5s執行一次了,證明作業持久化數據庫成功。
?
實驗3:我們在上面的數據庫表中發現一個現象,第一個字段都為Sched_Name,即調度器的名稱,而且默認都為QuartzScheduler,那么如何增加多個不同名稱的調度器呢?獲取的時候又是怎么獲取指定的調度器呢?都是通過下面的這句代碼配置:
properties.Add("quartz.scheduler.instanceName", "Ypf1Scheduler");?分享完整代碼:
var properties = new NameValueCollection();//SQLServer版本properties.Add("quartz.dataSource.myDS.provider", "SqlServer-20");//表名前綴(可有可無)//properties.Add("quartz.jobStore.tablePrefix", "QRTZ_");//數據庫連接字符串properties.Add("quartz.dataSource.myDS.connectionString", "Data Source=.;Initial Catalog=quartz;User ID=sa;Password=123456");//properties.Add("quartz.dataSource.myDS.connectionString", "Server =.;Database = quartz;Trusted_Connection =True;"); //JobStore設置(JobStoreTX: 帶有事務;JobStoreCMT:不帶有事務)//存儲類型properties.Add("quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz");//數據源名稱properties.Add("quartz.jobStore.dataSource", "myDS");//驅動類型properties.Add("quartz.jobStore.driverDelegateType", "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz");//Scheuler的名稱,用于處理多個調度器的情況(指定和獲取都是用這句代碼,如果不指定的話,默認均為QuartzScheduler名稱){properties.Add("quartz.scheduler.instanceName", "Ypf1Scheduler");}var factory = new StdSchedulerFactory(properties);IScheduler scheduler = factory.GetScheduler();var job = JobBuilder.Create<HelloJob4>().WithIdentity("ypfJob1", "ypfJobGroup1").Build();var trigger = TriggerBuilder.Create().WithIdentity("ypfTrigger1", "ypfTriggerGroup1").WithCronSchedule("/10 * * * * ?").Build();//Scheduler只要存在相同的job名稱,將不在關聯 (這里需要根據實際要求來處理)if (!scheduler.CheckExists(job.Key)){scheduler.ScheduleJob(job, trigger);}scheduler.Start();發現數據中上述的幾張表中多了一條數據:
?
?
PS:前面有博友【?搵中求勝】給我留言提示集群的問題,這里借助他的話給大家一個提醒:
在使用?Quartz.Impl.AdoJobStore?做集群時,一旦出現連接超時或者底層的SQL錯誤,這個Job將徹底堵住,即使數據庫連接恢復該JOB也得不到恢復,繼承自IJob的Execute方法將不會被調用。
因此,必須有一個Timer對這些超時未執行的Job做重置或者移除再加入(切誤參考網上DEMO做一個Manager繼承IJob,因為Manager也被堵住了)
?
二. 雙機熱備的集群模式
集群的兩種形式:
1.讀寫分離:即master - slave,在SQLServer通過“發布-訂閱”來實現,寫是落庫到master,讀從slave中,一個主多個從。
2.雙機熱備:即一主多備,高可用,主掛掉了,備會自動頂上去, Quartz.Net集群采用的就是這種形式(備用服務啟動,最短大約需要7.5s)。
配置代碼在持久化的基礎上多了兩句:
  properties["quartz.jobStore.clustered"] = "true";
   properties["quartz.scheduler.instanceId"] = "AUTO";
?下面分享完成的一段代碼:
1 var properties = new NameValueCollection();2 3 properties["quartz.dataSource.sqlserver.provider"] = "SqlServer-20";4 properties["quartz.dataSource.sqlserver.connectionString"] = @"Data Source=.;Initial Catalog=quartz;User ID=sa;Password=123456";5 properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";6 //注意這個名字改為了sqlserver,上面的都要跟著改,也可以改為別的名7 properties["quartz.jobStore.dataSource"] = "sqlserver";8 properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";9 10 //cluster 集群指定 11 properties["quartz.jobStore.clustered"] = "true"; 12 properties["quartz.scheduler.instanceId"] = "AUTO"; 13 14 //Scheuler的名稱,用于處理多個調度器的情況(指定和獲取都是用這句代碼,如果不指定的話,默認均為QuartzScheduler名稱) 15 { 16 properties.Add("quartz.scheduler.instanceName", "QuartzSchoolScheduler"); 17 } 18 19 var factory = new StdSchedulerFactory(properties); 20 var scheduler = factory.GetScheduler(); 21 var job = JobBuilder.Create<HelloJob4>() 22 .WithIdentity("job3", "jobGroup3") 23 .Build(); 24 //trigger 2s執行一次 25 var trigger = TriggerBuilder.Create() 26 .WithIdentity("trigger3", "triggerGroup3") 27 .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever()) 28 .Build(); 29 var isExists = scheduler.CheckExists(job.Key); 30 if (!isExists) 31 { 32 //開始調度 33 scheduler.ScheduleJob(job, trigger); 34 } 35 scheduler.Start();數據庫表的變化:
?QRTZ_SIMPROP_TRIGGERS 表: ?(與cron的trigger存放的位置不同)
?QRTZ_JOB_DETAILS 表:
?
?QRTZ_TRIGGERS 表:
?QRTZ_FIRED_TRIGGERS 表:
?
運行結果:
? 生成一下代碼,直接在bin文件里打開,然后再打開一個,發現第一個正常運行,第二個不運行。
?
關閉第一個客戶端,7.5s后發現第二個正常啟動運行,驗證雙機熱備。
?
?
?
總結
以上是生活随笔為你收集整理的第十节: 利用SQLServer实现Quartz的持久化和双机热备的集群模式的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 俞敏洪回应直播走红:可能一时热闹 不能骄
 - 下一篇: WSS 数据库表中的 UserInfo