基于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 技术专家刘洋在活动上做了<基于 ...
随机推荐
- 激活jws.mono的图像处理
不得不说,jws.mono真的给我们带来了很大的便利,它免除了我们编译Linux.NET所带来的烦恼,节省了我们的时间.但是金无足赤人无完人,虽然jws.mono已经大致能够提供与我们自行编译相同的效 ...
- 介绍两个Ubuntu上的桌面小工具
经常使用Windows10,Sticky Notes和壁纸自动切换功能挺好用的.我经常会使用Sticky Notes来记录一些信息,内容是实时保存的,而且启动的时候会自动显示在桌面上.其实Ubuntu ...
- 使用Hexo搭建专属Blog
喜欢折腾的自己最开始在博客园有仿写几篇Blog,虽也可以自己改变风格,可是到底不是独立的一块儿地方,要知道独立的才是自己的;有属于自己独立的域名和Blog,真真是一件很爽的存在.在各种大牛的分享下在G ...
- 携程App的网络性能优化实践
首先介绍一下携程App的网络服务架构.由于携程业务众多,开发资源导致无法全部使用Native来实现业务逻辑,因此有相当一部分频道基于Hybrid实现.网络通讯属于基础&业务框架层中基础设施的一 ...
- hbase 1.1.7在centor6.5安装过程
1.自己安装的最新版一直没成功,换成了1.1.7稳定版的.中间遇到的问题记录下 1) jdk 用的1.7版本的,安装过程省略. 2)下载hbase zip包:https://mirrors.tuna ...
- sublime text3同时编辑多行
sublime text3同时编辑多行 下面的才是正确的姿势,之前一直是shift + 右键 拖啊. http://stackoverflow.com/questions/32127604/how-t ...
- 浅析Yii2的view层设计
Yii2.0的view层提供了若干重要的功能:assets资源管理,widgets小组件,layouts布局... 下面将通过对Yii2.0代码直接进行分析,看一下上述功能都是如何实现的,当然细枝末节 ...
- Jetstrap 在线构建 Bootstrap 的工具
Jetstrap 是一个 100% 基于 Web 的 Twitter Bootstrap 构建工具,无需下载软件,只需登录并构建即可.并且别人可以访问你构建的产品.
- Android,App 常用图标尺寸规范
程序启动图标(Logo): 小屏ldpi() 36 x 36 px. 中屏mdpi(160dpi):48*48px 大屏hdpi(240dpi):72*72px 特大屏xhdpi(320dpi):96 ...
- 深入理解闭包系列第三篇——IIFE
× 目录 [1]实现 [2]用途 前面的话 严格来讲,IIFE并不是闭包,因为它并不满足函数成为闭包的三个条件.但一般地,人们认为IIFE就是闭包,毕竟闭包有多个定义.本文将详细介绍IIFE的实现和用 ...