Quartz.NET 3.0.7 + MySql 動態調度作業+動態切換版本+多作業引用同一程序集不同版本+持久化+集群(二)


 

Quartz.NET 3.0.7 + MySql 動態調度作業+動態切換版本+多作業引用同一程序集不同版本+持久化+集群(一)

Quartz.NET 3.0.7 + MySql 動態調度作業+動態切換版本+多作業引用同一程序集不同版本+持久化+集群(三)

Quartz.NET 3.0.7 + MySql 動態調度作業+動態切換版本+多作業引用同一程序集不同版本+持久化+集群(四)

 

上篇文章搞定了第一個功能.

1.利用反射動態創建Job;

2.調度服務如何知道有新的任務來了?是調度服務輪詢數據庫?還是管理后台通知調度服務?又或者遠程代理?

3.需要一個管理后台,提供啟動,暫停,恢復,停止等功能;

4.至於集群,Quartz.NET 本身就提供該功能,只不過要使用它的持久化方案而已.這個點只需要在配置文件上做做手腳就可以了,並不需要怎么開發.

5.管理后台如何實現啟動,暫停,恢復,停止等功能?靠遠程代理?還是通過其他方式?

 

接下來解決剩下的問題.

我一直認為世間萬物,塵歸塵,土歸土,本質都是一樣的.

動物與動物交流,機器與機器交流,兩個應用程序之間的交流,管你是什么東西交流,都跟人與人交流一樣.

要么你不停的問他,要么你等他告訴你.

再不濟,你倆都看對方不順眼,不想彼此直接交流,於是找來一個中間人.

他告訴中間人,中間人告訴你,

或者中間人不停的問他,有了消息,中間人再告訴你.

又或者你想要什么消息了,就去問中間人.中間人告訴你沒有,那就沒有.中間人說"我找一找","誒,這里有.來給你消息"

我認為其實就是這么回事兒,當然,我入行不久,理解還不夠深入.不過目前我覺得這樣理解能解決問題,就夠了.

學習講究的是方法,一來就研究到最底層,不是明智之舉.等哪天發現這么理解不能解決問題,這么理解有問題的時候,再深入研究也不遲.

還是那句話,路要一步一步走,飯要一口一口吃.存在的就是合理的.

 

當我們在管理后台新增一個作業的時候,作業的信息,比如名稱,時間表達式,程序集物理路徑,作業類型的完全限定名等,我們肯定是要找張表單獨存起來的,所以這里需要新建一張表:

CREATE TABLE `jobinfo` (
  `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
  `SchedName` varchar(20) CHARACTER SET utf8mb4 NOT NULL COMMENT '調度器名稱',
  `JobName` varchar(50) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '作業名稱',
  `JobGroup` varchar(20) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '作業組',
  `Cron` varchar(50) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '時間表達式',
  `Second` int(11) NOT NULL DEFAULT '0' COMMENT '間隔時間,單位:秒',
  `AssemblyPath` varchar(250) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '作業程序集物理路徑',
  `ClassType` varchar(100) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '作業完全限定名',
  `StartTime` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '作業開始時間',
  `CreateTime` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '作業創建時間',
  `ProjectTeam` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '項目組',
  `IsDeleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除 0:否 1:是',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
View Code

那么這些數據如何讓調度服務知道呢?(由於調研 Quartz,NET 框架的時候,看到很多大神說,IIS 回收池有坑.所以我就沒考慮把調度服務和管理后台集成在一起)

我第一版做的輪詢 ,就是在調度服務啟動的時候,啟動一個預先已經建好的輪詢job(輪詢的間隔時間盡量短一點,調低啞火忍耐時間,設置好失火策略),

輪詢job掃描這張表(下簡稱:作業表),然后根據表里的某個字段來判斷是否已經啟動了該job.

同時,當Job監聽器監聽到本次輪詢job執行完成后,暫停它,避免無謂的輪詢.

當管理后台新增了一個作業時,就通過遠程代理對象恢復該輪詢job.

我自以為這個方案很牛B,或者有那么一點點小"聰明".

但是,我后來把這個方案干掉了.

因為要使用遠程代理,管理后台就必須要 安裝 Quartz.NET ,這一點我感覺很不爽.我只想在調度服務一個地方安裝它.

這里插一句.

為了實現遠程代理,網上找了好多代碼,各種配置,都失敗了,不知道是我沒copy對,還是版本問題.

這里奉上我自己研究出來的,實測可用的代碼.至於遠程代理的配置文件,就不貼出來了,網上太多了.

                RemotingSchedulerProxyFactory proxyFactory = new RemotingSchedulerProxyFactory
                {
                    Address = "tcp://127.0.0.1:555/QuartzScheduler" }; var schedulerProxy = proxyFactory.GetProxy();

 

第二版,也就是目前采用的方案:

在調度服務內利用 owinself 組件內置一個api接口,接收管理后台的請求,拿到作業的數據后,實現該作業的啟動,暫停,恢復,停止等操作. 

因此,管理后台不需要安裝 Quartz.NET 組件了,只需要操作一下作業表,把作業的信息post給調度服務內置的api接口即可.

整個設計如下:

(03 調度服務框架核心 中的 Middleware 大家可以不用理它,僅僅是我拿來練手用的)

引用關系如下:

Host 引用 Service ,Service 里面主要是初始化調度器,啟動API監聽方面的代碼;

Service 引用 Api , Api 接收管理后台的請求

Api 引用 Logic ,Logic 里面就是具體的對Job的啟動,暫停,恢復等操作了.

另外, Api , Logic 都需要應用 Model

Logic 還需要引用 BaseJob 

管理后台與調度框架沒半毛錢聯系.(當然,Model還是要引用一下)

個人覺得這個設計耦合度比較低了.不過,還是那句話,任何事物都要辯證來看,耦合度是低了,開發量就相對多了一些.

是時候看看界面了,MVC做的,很清(jian)爽(lou)吧!我的水平實在有限,就這個界面我還是網上抄的模板,當然也參考了這位前輩對Quartz.NET使用方面的一些思路.原諒我,帖子找不到了....

像很多功能,比如觸發器的觸發機制選擇,執行次數,失火策略等,我就沒有在頁面上體現了,而是在調度框架內部暫時寫死了.一是時間來不及,二是公司的調度任務基本都差不多,沒有什么大的區別.不過以后肯定還是要加上.比如我只想今天下午執行10次等等

 對這個界面做一個簡單的說明:

  • 從"編號"到"程序集",這些字段的值來自作業表 jobInfo.
  • "狀態","開始時間","上次執行","下次執行"4個字段來自官方的 qrtz_triggers 表.
  • 頁面暫時不是實時的,要看最新狀態需要F5刷新.

對於"調度器名稱"字段需要特別說明.

整個框架開發到一半的時候,來了個新需求:

要同時開多個調度服務(控制台程序)調度不同的任務,但是用同一張數據庫表,同一個管理后台來管理.

比如現在已經啟了一個控制台程序了,管理了10個任務;

再啟一個控制台程序,管理另外10個任務,但是管理后台還是同一個,數據庫表還是同一張.注意,不是集群,只是想分開管理任務而已.

基於這個需求,所以設計了"調度器名稱"字段.

了解持久化方案的朋友肯定知道,在quartz.config配置文件中有這么一行:

  quartz.scheduler.instanceName = wechat

我的方案就是利用這句配置,

一個控制台程序(宿主)就是一個調度器,同時,將api地址放到控制台程序的配置文件中:

<add key="ApiAddress" value="http://localhost:25250" />

當我們再開一個控制台程序時,(注意,不是集群),就需要同時修改上面兩個配置,

比如新的控制台程序的配置及新的quartz.config如下:

 quartz.scheduler.instanceName = refuge

<add key="ApiAddress" value="http://localhost:25251" />

那么,這時候管理后台就需要增加如下配置了:

 <add key="wechat" value="http://localhost:25250" />
 <add key="refuge" value="http://localhost:25251" />

當我們點擊按鈕,發送請求前,先根據這個job的 "調度器名稱" 字段從配置文件中獲取它應該請求的地址.

為了做到絕對安全,我在控制台程序的api中添加了過濾器,操作請求過來的時候,檢查傳過來的job數據中的"調度器名稱"是否和該控制台程序中的調度器名稱一樣.不一樣則不做任何操作.

效果:

 

既然說到這個份上了,順便把集群也說了.配置文件就不貼了,網上太多.

Quartz.NET 的集群功能到底是個什么功能?它實際覆蓋兩個功能:

  • 解決單點問題
  • 均衡服務器壓力

解決單點問題

簡單說就是啟動兩個控制台程序,作業只會被一個控制台程序調度,當其中一個掛了,另外一個立馬開始工作.

由於我這個框架內置了api,api監聽地址肯定不能重復,所以要使用集群,必須修改api地址.

那么在集群模式下,管理后台怎么知道應該請求哪個api呢?

我們先看看效果,然后再解釋.

假設現在有兩個控制台應用程序,配置如下,調度器名稱都叫 "wechat",並且本身已經存在一個Job了.

控制台1:

  <add key="ApiAddress" value="http://localhost:25250" />

控制台2:

  <add key="ApiAddress" value="http://localhost:25260" />

效果圖: 

 

可以清楚的看到,下面的控制台程序並沒有執行Job,現在我們關掉上面的控制台,

我是20秒的時候關閉的,過了16秒,下面的控制台開始執行了.

現在來解釋下,管理后台的操作到底請求哪個api.

可能會有朋友認為,肯定要請求 25250 ,因為 25260 的控制台處於"備用"狀態,請求它沒效果.

事實上,這樣理解是錯的.

就算請求發送到 25260 控制台,雖然表面上這個控制台的調度器是處於"備用"狀態,但實際上它只是"待命"而已,有請求過來,它依然能"干活".

"改革春風吹滿地",實踐是檢驗真理的唯一標准.

還是上面兩個控制台,配置文件不變,現在修改一下管理后台的配置文件,api地址修改為 25260

   <add key="wechat" value="http://localhost:25260" />

運行效果:

21:18:00左右的時候 ,我通過管理后台啟動了 Job2,可以看到 25260 所在的控制台開始干活了.

 

所以,根本不用擔心,以集群的方式運行多個宿主的時候,管理后台應該請求哪一個api,而事實上,我們應該在管理后台的配置文件中,把所有集群的api地址都寫上:

    <add key="wechat" value="http://localhost:25250,http://localhost:25260" />

然后,請求的時候,判斷地址是否被監聽,只要被監聽了,post過去就不會有問題.
比如上面這個配置,如果某一天 25250 控制台掛了,無所謂,25260 不還在么?請求發送到 25260 就OK了.我們要做的僅僅是在請求前判斷一下這個地址是否已被監聽就行了,沒被監聽,就換一個.

均衡服務器壓力

這個就直接上圖吧!

 

下面來解釋一下:

首先,4個Job的時間表達式都一樣: 0/30 * * * * ?

我先啟動上面的控制台,在22:38:00 秒,4個Job都執行完成后,我啟動了下面的控制台,可以看到,"負載均衡"是起到了效果的.但是,4個Job在下面的控制台都各自重復了一次.

而且不管Job的間隔時間是多久,不過失火策略是什么,不管啞火的忍耐時間是多久,不管我隔多久啟動下面的控制台,我做了很多實驗,都會重復.而且遲早會重復一次,比如上面的 Job3 .但是只會重復一次.

這個有點小"坑"...我反復檢查了我的代碼,感覺不是代碼層面的問題.

我不知道這到底原因是什么?有沒有哪位前輩知道的?能否告知一下,或者大概可能是哪個方面的原因?

先到這吧!太晚了,明天繼續寫。

 

躺在床上,想起一個問題,判斷api地址是否被監聽不對!

我的集群是在兩個服務器上.我去......

我傻逼了......媽蛋......睡覺💤


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM