基于Quartz.NET构建自己的动态作业调度器
在日常的开发中,运行定时任务基本上已经是很普遍的需求了,可以通过windows服务+timer组件来实现,也可以使用第三方框架来集成,Quartz.NET就是一款从JAVA的Quartz移植过来的一个不错的作业调度组件,但是当我们把作业都写好,并部署完成的时候,管理成为了很麻烦的事情,因此我基于Quartz.NET,又简单做了一下封装,来实现作业动态管理。
首先作业动态管理包含以下几个核心点
- 应用程序动态加载器
- 作业管理(运行)池
- 动态启动/停止/卸载作业
Quzrtz.NET怎么用我这里就不再讲解了,百度上很多。
主要有三个核心模块,Job,Trigger和Schedule,
Job就是每一个作业,Trigger就是作业执行策略(多长时间执行一次等),Schedule则把Job和Tigger装载起来
Job和Tigger可以随意搭配装载到Schedule里面运行
接下来讲解实现的思路
先定义一个类库,类库只包含一个类,BaseJob ,里面只有一个Run()方法
之后我们实现的每一个作业都是继承自这个类,实现Run()方法即可(每个作业都作为一个独立的类库,引用这个只有一个类的类库)
public abstract class BaseJob:MarshalByRefObject,IDisposable
{
public abstract void Run();
}
接下来建立我们的作业管理核心类库Job.Service nuget安装Quartz.NET
然后新建类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());
});
}
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等,思路就是发布一条广播给所有的作业调度框架,作业调度框架根据广播消息来进行作业的装载,启动,停止,卸载等操作。
至此,一个基本的动态作业调度框架就结束了。
基于Quartz.NET构建自己的动态作业调度器的更多相关文章
- 在微信框架模块中,基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作
最近把微信框架的前端改造一下,在原来基于Bootstrap框架基础上的微信后台管理,增加一套Vue&Element的前端,毕竟Vue的双向绑定开发起来也还是很方便的,而且Element本身也提 ...
- RDIFramework.NET框架基于Quartz.Net实现任务调度详解及效果展示
在上一篇Quartz.Net实现作业定时调度详解,我们通过实例代码详细讲解与演示了基于Quartz.NET开发的详细方法.本篇我们主要讲述基于RDIFramework.NET框架整合Quartz.NE ...
- 控制台基于Quartz.Net组件实现定时任务调度(一)
前言: 你曾经需要应用执行一个任务吗?比如现在有一个需求,需要每天在零点定时执行一些操作,那应该怎样操作呢? 这个时候,如果你和你的团队是用.NET编程的话,可以考虑使用Quartz.NET调度器.允 ...
- Window服务基于Quartz.Net组件实现定时任务调度(二)
前言: 在上一章中,我们通过利用控制台实现定时任务调度,已经大致了解了如何基于Quartz.Net组件实现任务,至少包括三部分:job(作业),trigger(触发器),scheduler(调度器). ...
- 基于Dubbo框架构建分布式服务(一)
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...
- Quartz 在 Spring 中如何动态配置时间--转
原文地址:http://www.iteye.com/topic/399980 在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度. 有关调度的实现我就第一就想到了Quartz这个开源 ...
- 基于Dubbo框架构建分布式服务
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...
- [转载] 基于Dubbo框架构建分布式服务
转载自http://shiyanjun.cn/archives/1075.html Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务 ...
- Polaristech 刘洋:基于 OpenResty/Kong 构建边缘计算平台
2019 年 3 月 23 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·北京站,Polaristech 技术专家刘洋在活动上做了<基于 ...
随机推荐
- TaintDroid剖析之DVM变量级污点跟踪(下篇)
TaintDroid剖析之DVM变量级污点跟踪(下篇)作者:简行.走位@阿里聚安全 1 回顾 在上一章节中我们详细分析了TaintDroid对DVM方法参数和方法变量的变量级污点跟踪机制,现在我们 ...
- Hadoop学习笔记—11.MapReduce中的排序和分组
一.写在之前的 1.1 回顾Map阶段四大步骤 首先,我们回顾一下在MapReduce中,排序和分组在哪里被执行: 从上图中可以清楚地看出,在Step1.4也就是第四步中,需要对不同分区中的数据进行排 ...
- iOS开发系列--C语言之存储方式和作用域
概述 基本上每种语言都要讨论这个话题,C语言也不例外,因为只有你完全了解每个变量或函数存储方式.作用范围和销毁时间才可能正确的使用这门语言.今天将着重介绍C语言中变量作用范围.存储方式.生命周期.作用 ...
- RPC通信框架——RCF介绍
现有的软件中用了大量的COM接口,导致无法跨平台,当然由于与Windows结合的太紧密,还有很多无法跨平台的地方.那么为了实现跨平台,支持Linux系统,以及后续的分布式,首要任务是去除COM接口. ...
- 有一个团队协同工具,叫Worktile
项目管理,本是一个老生常谈的话题,曾几何时大碗云集在这个市场,其中不乏出现像微软.SAP.IBM.用友这样的名字.复杂而又冗繁的流程控制,让人们划分成两类人,一类是会使用这些工具和系统的人,另一类是不 ...
- Hystrix框架2--超时
timeout 在调用第三方服务时有些情况需要对服务响应时间进行把控,当超时的情况下进行fallback的处理 下面来看下超时的案例 public class CommandTimeout exten ...
- Docker学习笔记
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互之间不会有任何 ...
- 我和linux的第二十二天
这几天学校的事情比较多,空闲时间也有,但没有利用起来.前些天听国学课,发觉自己心性还是不很成熟,以前自觉遇到君子应用君子的方法相处,遇到小人用小人的方法对待,老师一句话,疏清了自己.当我们用小人的方法 ...
- SmtpClient发邮件时为什么用MailMessage.From而不用MailMessage.Sender
今天在看C#高级编程(第9版)的时候,在768页看到这样的一段代码 SmtpClient sc = new SmtpClient(); sc.Host = "邮箱服务器地址"; M ...
- Codeforces Round #327 (Div. 2) B. Rebranding C. Median Smoothing
B. Rebranding The name of one small but proud corporation consists of n lowercase English letters. T ...