在平常的開發中,運行定時任務基本上已是很廣泛的需求了,能夠經過windows服務+timer組件來實現,也可使用第三方框架來集成,Quartz.NET就是一款從JAVA的Quartz移植過來的一個不錯的做業調度組件,可是當咱們把做業都寫好,並部署完成的時候,管理成爲了很麻煩的事情,所以我基於Quartz.NET,又簡單作了一下封裝,來實現做業動態管理。web
首先做業動態管理包含如下幾個核心點數據庫
Quzrtz.NET怎麼用我這裏就再也不講解了,百度上不少。windows
主要有三個核心模塊,Job,Trigger和Schedule,服務器
Job就是每個做業,Trigger就是做業執行策略(多長時間執行一次等),Schedule則把Job和Tigger裝載起來app
Job和Tigger能夠隨意搭配裝載到Schedule裏面運行框架
接下來說解實現的思路ide
先定義一個類庫,類庫只包含一個類,BaseJob ,裏面只有一個Run()方法ui
以後咱們實現的每個做業都是繼承自這個類,實現Run()方法便可(每一個做業都做爲一個獨立的類庫,引用這個只有一個類的類庫)this
public abstract class BaseJob:MarshalByRefObject,IDisposable { public abstract void Run(); }
接下來創建咱們的做業管理核心類庫Job.Service nuget安裝Quartz.NETspa
而後新建類JobImplement.cs實現Quartz.NET的IJob接口
這樣咱們就能夠在裏面經過咱們本身寫的做業調度容器獲取到動態加載的Job信息,並運行Job的run方法,來實現動態調度了(做業調度容器裏的做業如何裝載進去的在文章後面講解)
jobRuntimeInfo是咱們本身定義的實體類,裏面包含了BaseJob,AppDomain,JobInfo 三個信息
JobInfo是做業在上傳到做業動態調度框架時所須要填寫的做業基本信息
public class JobImplement : IJob { public void Execute(IJobExecutionContext context) { try { long jobId = context.JobDetail.JobDataMap.GetLong("JobId"); //從做業調度容器裏查找,若是找到,則運行 var jobRuntimeInfo = JobPoolManager.Instance.Get(jobId); try { jobRuntimeInfo.Job.TryRun(); } catch (Exception ex) { //寫日誌,任務調用失敗 ConnectionFactory.GetInstance<Provider.JobStateRepository>() .Update(new Provider.Tables.JobState() { JobId = jobId, RunState = (int) Provider.DirectiveType.Stop, UpdateTime = DateTime.Now }); Common.Logging.LogManager.GetLogger(this.GetType()).Error(ex.Message, ex); } } catch (Exception ex) { Common.Logging.LogManager.GetLogger(this.GetType()).Error(ex.Message, ex); //調用的時候失敗,寫日誌,這裏錯誤,屬於系統級錯誤,嚴重錯誤 } } }
JobRuntimeInfo
public class JobRuntimeInfo { public AppDomain AppDomain; public BaseJob Job { get; set; } public JobInfo JobModel { get; set; } }
JobInfo
public class JobInfo { public long JobId { get; set; } public string JobName { get; set; }public string TaskCron { get; set; } public string Namespace { get; set; } public string MainDllName { get; set; } public string Remark { get; set; } public string ZipFileName { get; set; } public string Version { get; set; } public DateTime? CreateTime { get; set; } }
接下來咱們來說解這個做業是如何執行的
1.經過一個上傳頁面把做業類庫打包爲zip或者rar上傳到服務器,並填寫Job運行的相關信息,添加到數據庫裏
2.上傳完成以後發佈一條廣播消息給全部的做業調度框架
3.做業調度框架接收到廣播消息,從數據庫獲取JobInfo,自動根據上傳的時候填寫的信息(見上面的JobInfo類的屬性),自動解壓,裝載到AppDomain裏
public class AppDomainLoader { /// <summary> /// 加載應用程序,獲取相應實例 /// </summary> /// <param name="dllPath"></param> /// <param name="classPath"></param> /// <param name="appDomain"></param> /// <returns></returns> public static BaseJob Load(string dllPath, string classPath, out AppDomain appDomain) where T : class { AppDomainSetup setup = new AppDomainSetup(); if (System.IO.File.Exists($"{dllPath}.config")) setup.ConfigurationFile = $"{dllPath}.config"; setup.ShadowCopyFiles = "true"; setup.ApplicationBase = System.IO.Path.GetDirectoryName(dllPath); appDomain = AppDomain.CreateDomain(System.IO.Path.GetFileName(dllPath), null, setup); AppDomain.MonitoringIsEnabled = true; BaseJob obj = (BaseJob) appDomain.CreateInstanceFromAndUnwrap(dllPath, classPath); return obj; } /// <summary> /// 卸載應用程序 /// </summary> /// <param name="appDomain"></param> public static void UnLoad(AppDomain appDomain) { AppDomain.Unload(appDomain); appDomain = null; } }
4.由於做業都繼承了BaseJob類,因此AppDomain裏的入口程序就是JobInfo.Namespace,反射實例化以後強制轉換爲BaseJob,而後建立一個JobRuntime對象,添加到JobPoolManager裏,JobPoolManager裏維護全部的正在運行的Job
5.根據JobInfo.TaskCron(時間表達式)建立Trigger,建立一個JobImplement,並在Context里加一個JobId,保證在JobImplement的Run運行的時候可以從JobPoolManager裏獲取到Job的基本信息,以及BaseJob的事例,並調用JobRuntime=>BaseJob=>Run()方法來運行實際的做業
public class JobPoolManager:IDisposable { private static ConcurrentDictionary<long, JobRuntimeInfo> JobRuntimePool = new ConcurrentDictionary<long, JobRuntimeInfo>(); private static IScheduler _scheduler; private static JobPoolManager _jobPollManager; private JobPoolManager(){} static JobPoolManager() { _jobPollManager = new JobPoolManager(); _scheduler = StdSchedulerFactory.GetDefaultScheduler(); _scheduler.Start(); } public static JobPoolManager Instance { get { return _jobPollManager; } } static object _lock=new object(); public bool Add(long jobId, JobRuntimeInfo jobRuntimeInfo) { lock (_lock) { if (!JobRuntimePool.ContainsKey(jobId)) { if (JobRuntimePool.TryAdd(jobId, jobRuntimeInfo)) { IDictionary<string, object> data = new Dictionary<string, object>() { ["JobId"]=jobId }; IJobDetail jobDetail = JobBuilder.Create<JobImplement>() .WithIdentity(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group) .SetJobData(new JobDataMap(data)) .Build(); var tiggerBuilder = TriggerBuilder.Create() .WithIdentity(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group); if (string.IsNullOrWhiteSpace(jobRuntimeInfo.JobModel.TaskCron)) { tiggerBuilder = tiggerBuilder.WithSimpleSchedule((simple) => { simple.WithInterval(TimeSpan.FromSeconds(1)); }); } else { tiggerBuilder = tiggerBuilder .StartNow() .WithCronSchedule(jobRuntimeInfo.JobModel.TaskCron); } var trigger = tiggerBuilder.Build(); _scheduler.ScheduleJob(jobDetail, trigger); return true; } } return false; } } public JobRuntimeInfo Get(long jobId) { if (!JobRuntimePool.ContainsKey(jobId)) { return null; } lock (_lock) { if (JobRuntimePool.ContainsKey(jobId)) { JobRuntimeInfo jobRuntimeInfo = null; JobRuntimePool.TryGetValue(jobId, out jobRuntimeInfo); return jobRuntimeInfo; } return null; } } public bool Remove(long jobId) { lock (_lock) { if (JobRuntimePool.ContainsKey(jobId)) { JobRuntimeInfo jobRuntimeInfo = null; JobRuntimePool.TryGetValue(jobId, out jobRuntimeInfo); if (jobRuntimeInfo != null) { var tiggerKey = new TriggerKey(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group); _scheduler.PauseTrigger(tiggerKey); _scheduler.UnscheduleJob(tiggerKey); _scheduler.DeleteJob(new JobKey(jobRuntimeInfo.JobModel.JobName, jobRuntimeInfo.JobModel.Group)); JobRuntimePool.TryRemove(jobId, out jobRuntimeInfo); return true; } } return false; } } public virtual void Dispose() { if (_scheduler != null && !_scheduler.IsShutdown) { foreach (var jobId in JobRuntimePool.Keys) { var jobState = ConnectionFactory.GetInstance<Job.Provider.JobStateRepository>().Get(jobId); if (jobState != null) { jobState.RunState = (int) DirectiveType.Stop; jobState.UpdateTime = DateTime.Now; ConnectionFactory.GetInstance<Job.Provider.JobStateRepository>().Update(jobState); } } _scheduler.Shutdown(); } } }
而後咱們除了作了一個web版的上傳界面以外,還能夠作全部的job列表,用來作Start|Stop|Restart等,思路就是發佈一條廣播給全部的做業調度框架,做業調度框架根據廣播消息來進行做業的裝載,啓動,中止,卸載等操做。
至此,一個基本的動態做業調度框架就結束了。