在日常的开发中,运行定时任务基本上已经是很普遍的需求了,可以通过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. 走向面试之数据库基础:一、你必知必会的SQL语句练习-Part 1

    本文是在Cat Qi的参考原帖的基础之上经本人一题一题练习后编辑而成,非原创,仅润色而已.另外,本文所列题目的解法并非只有一种,本文只是给出比较普通的一种而已,也希望各位园友能够自由发挥. 一.三点一 ...

  2. Redis系列(二)-Hredis客户端设计及开源

    接上篇c#实现redis客户端(一),重新整理些了下. 阅读目录: 项目说明 Hredis设计图 单元测试场景 总结 项目说明 背景:因为有地方要用,而又没找到对sentinel良好支持的Net客户端 ...

  3. [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上)

    [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上) 本篇导读: 上篇介绍了常用的代码管理工具VSS,看了一下评论,很多同学深恶痛绝,有的甚至因为公司使用VS ...

  4. Android N 多窗口模式,你需要知道的一切

    Android N中最大.最引人注意的变化就是Mutil-window模式.对于一个开发者,我们最关心的就是:Mutil-window模式下怎么配置mutil-window模式.Activity的生命 ...

  5. RegSvr32注册OCX时报错

    RegSvr32注册OCX时报错. 错误1: 模块“dsoframer2007.ocx”已加载,但对 DllRegisterServer 的调用失败,错误代码为 0x80070005. 有关此问题的详 ...

  6. Castle中AdditionalInterfaces用法介绍

    首先见下图(图一),其中FooController是一个没有实现任何Interface的空类.需要实现的效果是:通过FooController对象调用FooService的Do方法.设置这一不常见的场 ...

  7. Spring学习记录(十一)---使用注解和自动装配

    Spring支持用注解配置Bean,更简便. 上面的组件,是根据实际情况配的.比如写的一个类,是做业务处理的,那就用注解@Service表示服务层组件,以此类推.将整体分成不同部分. 要在xml加入c ...

  8. myeclipse转到函数定义的方法去

    转到函数的定义CTRl+鼠标左击 myeclipse自动补全的快捷键 alt+/

  9. 面向对象设计之SRP(单一职责)原则

    SRP设计原则面向对象类设计的第一个原则,最优先考虑的因素 一个类应该有且仅有一个职责.所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因 引起该类变化,其 ...

  10. Divshot —— 在线的可视化网页设计

    Divshot 是一个在线网站,通过可视化方式进行 Web 界面的设计,并直接生成 HTML 和 CSS 代码.该工具提供常用的 Web UI 组件.界面基于 Twitter 的 Bootstrap  ...