在平常的开发中,运行定时任务基本上已是很广泛的需求了,能够经过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等,思路就是发布一条广播给全部的做业调度框架,做业调度框架根据广播消息来进行做业的装载,启动,中止,卸载等操做。
至此,一个基本的动态做业调度框架就结束了。