在日常的开发中,运行定时任务基本上已经是很普遍的需求了,可以通过windows服务+timer组件来实现,也可以使用第三方框架来集成,Quartz.NET就是一款从JAVA的Quartz移植过来的一个不错的作业调度组件,但是当我们把作业都写好,并部署完成的时候,管理成为了很麻烦的事情,因此我基于Quartz.NET,又简单做了一下封装,来实现作业动态管理。

  首先作业动态管理包含以下几个核心点

  1. 应用程序动态加载器
  2. 作业管理(运行)池
  3. 动态启动/停止/卸载作业

  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构建自己的动态作业调度器的更多相关文章

  1. 在微信框架模块中,基于Vue&Element前端,通过动态构建投票选项,实现单选、复选的投票操作

    最近把微信框架的前端改造一下,在原来基于Bootstrap框架基础上的微信后台管理,增加一套Vue&Element的前端,毕竟Vue的双向绑定开发起来也还是很方便的,而且Element本身也提 ...

  2. RDIFramework.NET框架基于Quartz.Net实现任务调度详解及效果展示

    在上一篇Quartz.Net实现作业定时调度详解,我们通过实例代码详细讲解与演示了基于Quartz.NET开发的详细方法.本篇我们主要讲述基于RDIFramework.NET框架整合Quartz.NE ...

  3. 控制台基于Quartz.Net组件实现定时任务调度(一)

    前言: 你曾经需要应用执行一个任务吗?比如现在有一个需求,需要每天在零点定时执行一些操作,那应该怎样操作呢? 这个时候,如果你和你的团队是用.NET编程的话,可以考虑使用Quartz.NET调度器.允 ...

  4. Window服务基于Quartz.Net组件实现定时任务调度(二)

    前言: 在上一章中,我们通过利用控制台实现定时任务调度,已经大致了解了如何基于Quartz.Net组件实现任务,至少包括三部分:job(作业),trigger(触发器),scheduler(调度器). ...

  5. 基于Dubbo框架构建分布式服务(一)

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...

  6. Quartz 在 Spring 中如何动态配置时间--转

    原文地址:http://www.iteye.com/topic/399980 在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度. 有关调度的实现我就第一就想到了Quartz这个开源 ...

  7. 基于Dubbo框架构建分布式服务

    Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...

  8. [转载] 基于Dubbo框架构建分布式服务

    转载自http://shiyanjun.cn/archives/1075.html Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务 ...

  9. Polaristech 刘洋:基于 OpenResty/Kong 构建边缘计算平台

    2019 年 3 月 23 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·北京站,Polaristech 技术专家刘洋在活动上做了<基于 ...

随机推荐

  1. ASP.Net请求处理机制初步探索之旅 - Part 3 管道

    开篇:上一篇我们了解了一个ASP.Net页面请求的核心处理入口,它经历了三个重要的入口,分别是:ISAPIRuntime.ProcessRequest().HttpRuntime.ProcessReq ...

  2. ES7之Decorators实现AOP示例

    在上篇博文CoffeeScript实现Python装潢器中,笔者利用CoffeeScript支持的高阶函数,以及方法调用可省略括符的特性,实现了一个类似Python装潢器的日志Demo.这只是一种伪实 ...

  3. 基于HttpModule的简单.NET网站授权方案

    摘要 本文介绍一种入门级的网站授权(注:这里所指的授权指的是注册码效果,而不是网站登陆时的身份授权)方案,仅供学习交流及对付小白客户使用.复杂的网站授权涉及网站加密等一系列复杂的技术,不做本文介绍内容 ...

  4. 说说SQL Server 网络配置

    打开Sql Server Configuration Manager,里面显示了SQL Server的网络配置,这些到底表示什么含义呢? 图一:MSSQLSERVER的协议 这些配置选项,其实就是为了 ...

  5. HTTPS那些事(二)SSL证书(转载)

    原创地址:http://www.guokr.com/post/116169/   从第一部分HTTP工作原理中,我们可以了解到HTTPS核心的一个部分是数据传输之前的握手,握手过程中确定了数据加密的密 ...

  6. 免安裝、免設定的 Hadoop 開發環境 - cloudera 的 QuickStart VM

    cloudera 的 QuickStart VM,為一種免安裝.免設定 Linux 及 Hadoop,已幫你建好 CDH 5.x.Hadoop.Eclipse 的一個虛擬機環境.下載後解壓縮,可直接以 ...

  7. Using assembly writing algorithm programs

    This's my first version.The logic is simple, just the selection sort. I spent much time learning how ...

  8. 【原】Python 用例:二进制写入和读取文件内容

    import pickle as p shoplistfile='shoplist.data' shoplist=['apple','carrot'] # because the dump opera ...

  9. TextView跑马灯效果

    转载:http://www.2cto.com/kf/201409/330658.html 一.只想让TextView显示一行,但是文字超过TextView的长度怎么办?在开头显示省略号 android ...

  10. EntityFramework之原始查询及性能优化(六)

    前言 在EF中我们可以通过Linq来操作实体类,但是有些时候我们必须通过原始sql语句或者存储过程来进行查询数据库,所以我们可以通过EF Code First来实现,但是SQL语句和存储过程无法进行映 ...