第十節: 利用SQLServer實現Quartz的持久化和雙機熱備的集羣模式 :

背景: 默認狀況下,Quartz.Net做業是持久化在內存中的,即 quartz.jobStore.type = "Quartz.Simpl.RAMJobStore, Quartz",這種模式有如下弊端:html

① 想在A服務器上控制B服務器上已經發布了的job和trigger不方便;git

② 很難實現Web端(寄宿在IIS上)管理做業,客戶端(發佈成服務)的這種模式。github

③ 最大弊端就是一旦服務器宕機或者重啓,調度器Schdeuler對應的全部做業(job、trigger及其對應關係)將丟失,不得不從新發布;sql

 解決方案:數據庫

  針對問題1. 能夠藉助Remote代理的模式,經過TCP協議在A服務器上直接獲取B服務器上的Scheduler,而後進行操做。(詳情點擊)服務器

  針對問題2. 客戶端做爲Server端進行調度的執行,Web端經過Remote模式獲取客戶端的中的Scheduler,而後進行做業的管理,問題是一旦客戶端端掛機,Web端是鏈接不上的ide

  針對問題3. 不管代理仍是不代理,只要Server端一掛機,保存在內存中的做業都會丟失,因此這個時候,咱們須要另闢蹊徑,將做業持久化進行遷移,好比遷移到數據庫中,這樣話,即便服務器宕機,數據庫中存儲的做業信息仍然存在,下次只須要開啓Scheduler便可,無須配置job和trigger了,同時也解決了上述問題1和問題2,即均可以直接修改數據庫便可。sqlserver

該章節也是爲開篇提出的目標三鋪最後一道路,下面着重介紹持久化SQLServer數據庫。ui

步驟1:準備數據庫腳本。spa

  下載地址爲: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後發現第二個正常啓動運行,驗證雙機熱備。

 

 

 

 

 

 

相關文章
相關標籤/搜索