【框架学习与探究之定时器--Quartz.Net 】

声明


本文欢迎转载原文地址:http://www.cnblogs.com/DjlNet/p/7572174.html


前言

这里相信大部分玩家之前现在都应该有过使用定时器的时候或者需求,例如什么定时发送邮件通知,定时筛选取消客户下单未支付的订单,定时数据备份或者归档清洗什么的诸如此类的需求,都是定时的作用的地方,类似比如:windows的计划任务、数据库的计划任务都是同样体现,那么相关于这方面的轮子或者发动机就孕育而生了,也有一直一来没怎么人使用的微软框架自带5种Timer系列等等......
额,关于今天要说的这个定时器框架,相信无论是java还是.net开发者都肯定听说过了,当然这个定时器有些年生了是个老字号了并不表示它就真的老了哦,去github:https://github.com/quartznet/quartznet看了才知道,虽然是移植过来的但是作者是一个人在维护呀,还很积极在commit和解答issues,这里还是佩服一下作者的勇气和那份责任,顺手Star一下加加油,而且最新的3.x也正在开发基于.net标准的做新的nuget package https://www.nuget.org/packages/Quartz/3.0.0-alpha3 开发,这里博主大致使用一下主要有一个亮点是把以前的依赖包都整合了,体现就是一个nuget包就OK了看不见什么Common.Logging之类的了,其中还有些Feature或者Fixes(见3.X文档),期待稳定正式版吧!!!...
然后关于这个框架的自述或者介绍什么的,园子文章很多啦或者官方文档https://www.quartz-scheduler.net介绍的都挺清楚的,其中文档中有12个学习课程(其中发现园友对文档的中文翻译,把园中关于此框架的部分文章看了一下,具体想去了解关键字搜索即可,不过较多是大致介绍和简单使用的居多,部分都是自我集成在自己或者公司的系统中去了,形成系统中的一环或者整个任务调度平台覆盖到系统层面之上,所以看如何设计整合和正确姿势使用才是关注的重点嘛!!),全部刷一遍也费不了多少时间的,也就是少几集电视嘛,然后这样就会对框架设计或者学习使用都基本有个大致了解,后面催牛的时候不至于让人家以为你在瞎BB呐,哈哈。那么,今天我们写文是为了记录什么呐?额,主要是博主本身在学习过程的遇到一些不解或者需要划重点的东西记录一下吧,233333......


Quartz.Net框架设计概览

这里有挺多的关键词构成了整个关系链的组织,俗话说得好,一张图胜过几百上千字,所以这里博主靠着拙劣的构想脑回路简述一下框架带来的大体设计思想。

额,这张图也花了一两小时完成责在归纳了关于此框架整体结构和组织构成,从中一些备注和标识可以看出,Quarzt.Net再设计上面还是中规中矩,能考虑到的都尽可能考虑了,例如什么具体执行任务与触发器以及调度器三者分离,实现了组件职责单一原则同时呐又可以复用组件一举两得,这样一来开发者就就可以按组件开发自由组合,再者就是运用了些许设计模式以及面向接口抽象编程的思想设计模式上面的功效就是让代码实现了抽象和细节剥离,也就是封装隔离了变化点,让后面的需求或者变化尽可能得可以掌控以助于对整体结构的造成冲击力降到最低,然后基于面向接口编程也使得后期开发者自主实现可替换性的组件替换时有了机会,所以从这个层面看一个框架或者一个组件,就可以大致知道作者当初在设计的时候,为什么要这样,以至于这样之后带来的好处,什么扩展性、易用性、灵活性等等也是从中体现的吧....那么在学习了人家的设计思想之后,再看看自己系统的代码可谓是有心无力呀,当然拉老项目固然是如此,不过呢对一个人思想上面的影响效果远大于一行代码来的更加长远,所以面对新项目的时候就可得好好构想一下,运用一下所谓的平衡术( 只有合适的框架没有最好的框架 )....


Quartz.Net文档划重点与验证

这里着重记录一下文档中个人觉得比较关键之处,此外会用代码的方式去验证,做到斤斤计较,哈哈,由于2.X与3.X差距还是有的,但是3.X还没正式版,所以我们的系统中集成的还是2.X,那么我们还是针对2.X文档来做学习哈,不过3.X文档博主已经撸了一遍了差距甚小,以及使用方式基本相似的,具体看版本迁移中的介绍也行...


Job注意事项

这里在官方文档Job一章中也有详细介绍,但是博主这里用自己的话理解一下,当然英文还可以的童鞋可以去看文档,博主还要翻译插件才能凑合阅读呐...

1、关于IJob与IJobDetail注意点实现接口IJob的类的实例只是代表了要具体执行的任务逻辑而已,而实现了IJobDetail的类的实例才是包含此任务的细节,通过JobBuilder.Create创建,但是这里的创建也正如上图所诉不是“真正的创建”而是传递了JobType在JobDetial中保存引用而已,而后面具体在scheduler.JobFactory中来接管IJob具体实例的创建工作,这样一来你的ioc容器就可以在自定义的XXXJobFactory中去自定义NewJob的创建过程,注入你的XXXService或者XXXRepository,当然也可以直接使用ServiceLoader.Resolve服务定位器模式在Execute中直接解析拿出来使用也可以,但是注意一点Quartz.Net是每次执行Job的时候都是会新创建一个Job实例的,所以注意ioc容器注入对象的生命周期的合理性。 大致去nuget上面搜索一下(某些包最近commit时间略显久远了),autofac、unity、Ninject都提供了第三方集成Quarzt.Net包,大致看了一下代码量很少只有几个类而已,编写套路大体相近某些还接管了ISchedulerFactory创建等,所以自己需要为Quarzt集成第三方ioc的时候,可以考虑借鉴代码自己实现。
2、JobDetail关键属性与Job.Execute异常处理
(1)当一个jobDetail的job运行执行的时间大于它的trigger触发器的间隔调度时间的时候,就会发生它的上次任务还没运行完,接着又开始了下一个任务,或者多个trigger同时触发执行同一个jobDetail的任务的时候都会造成这个jobDetail的job任务并发执行,通过在YourJob:IJobYourJob打上[DisallowConcurrentExecution],其实就是一个简单的属性类标记一下这个类而已,然后作用于具体对应的JobDetial实例(这句话很关键),然后JobDetail的Job执行逻辑在上述两种情况下都可以按照理想执行了,以上博主已经通过代码验证过了哈,注意:多个trigger绑定同一个jobdetail需要jobDetail->StoreDurably() +trigger-> ForJob() **
(2)YourJob标记: [PersistJobDataAfterExecution] 可以
记住jobDetail.JobDataMap的值,所以你可以在job的执行中修改它的值,在下一次执行时候可以拿到更新之后的值拉,所以在这种情况下需要记住上次状态,当然就需要 [DisallowConcurrentExecution] 来做支持拉,这个自然是可以理解的
(3)
Durability** 持久的存储作用于jobdetail,RequestsRecovery 请求恢复作用于jobdetail当出现崩溃类似场景时使用, JobExecutionException 当job执行时需要使用try-catch来截获所有异常,且再次向上次抛出异常需要包装成Quartz认识的异常类型JobExecutionException ,且可以设置JobExecutionException 的可用属性,当然你可以使用BaseJob之类的或者AOP(castle dynamic proxy)的方式来实现job执行当中的日志、异常处理等


Trigger其他说明

Trigger:上图基本介绍的差不多了,注意这里有个RepeatForever( 与RequestRecovery不要搞混了咯,2333楼主都看花眼了一不小心... )、以及RepeatCount可能是你挺常用的属性哦,这里补充一下Priority优先级默认是5,当出现资源争抢的时候例如:线程不够,会按照优先级来分配资源执行,以及每种Trigger有自己对应的熄火指令 Misfire Instructions就是在调度器关闭或者应用程序结束的时候或者调度线程资源不够的时候会发现Trigger暂时性的熄火,默认情况下直接使用智能策略就行了


JobStores 持久化与 Clustering 集群

这里官方对JobStores提供了两种模式,https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/job-stores.html,其中一般情况还是推荐使用 RAMJobStore,因为不论是存储拿数据还是CPU级别的调度都是最佳的,但是缺点也是比较明显的就是对需要持久化的信息在应用程序重启之后就丢失了,例如XXJob的上次激发时间最后执行时间等,所以框架自身提供了AdoJobStore 可以找到对应的 DB Provider配置 即可,然后 Clustering 集群肯定是基于上面的 AdoJobStore 数据库存储模式下的,可以解决负载均衡和故障转移的功能。
这里博主将会做实验去验证,同时在园子中搜索发现已经有同学去实践了,这里引用一下各位大大的博文地址(应该不会介意,嘻嘻),大家也可以参考学习:
http://www.cnblogs.com/knowledgesea/p/5145239.html Quartz.net持久化与集群部署开发详解
http://www.cnblogs.com/mushroom/p/4231642.html#3760225 Net作业调度(四)—quartz.net持久化和集群
http://www.cnblogs.com/lanxiaoke/p/6629481.html 任务调度之持久化(基于Quartz.net)
http://www.cnblogs.com/lanxiaoke/p/6637714.html 任务调度之集群(基于Quartz.net)
http://www.cnblogs.com/huangxincheng/p/6916246.html 使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】
这里博主这里就不在啰嗦怎么操作了(上文有仔细操作),直接去做实验验证即可,实验之前先说几个注意事项:
1、在持久化模式下面,当Scheduler调度器总是执行和线程池相当的任务job数的时候,数据库连接数尽量要保证是 ThreadPool线程数量+1 的状态,前提是没超过最大连接数
2、在集群的情况下,当jobDetail设置为 RequestsRecovery -> true 当前的JobDetail才有效果
3、集群配置:除了线程池数量,instanceId可以不同外,各个节点的配置必须是一样的
4、集群中节点的系统时间一致
5、注意持久化和集群模式下的配置项

在经过一番测试集群发现,Quartz.Net会自动冲裁不可用的节点,让一个可用节点来执行,内部有机制去测试每个节点的可用情况会定时去检测然后剔除,以及新晋节点的加入等,也正好体现了所谓的负载均衡和故障转移咯,博主测试环境:应用程序Console、数据库Sql server localdb,本机附带两个应用实例测试,发现切换节点时间差为16s...


Quartz.Net UI控制台

**CrystalQuartz : github地址:https://github.com/guryanovev/CrystalQuartz **至于集成方式、方法项目地址当中有说明拉...


总结

哇,不知不觉居然还花了挺多的时间去学习与研究这个框架,先是了解生态圈中的定时器框架比较流行的(当然也包含了Hangfire后面我们有机会也去学习学习),然后想着之前对Quartz有点懵懵懂懂的,所以就去系统的看了看官方文档了解了解人家的设计思想和实现,到了后面带着问题去看了些源代码哈,然后跟着官方的文档基本撸了一遍之后,包含了去园中也去阅读了其他博主的相关博客,以及周边衍生物的 CrystalQuartz UI控制台界面等等。
最后呐,各位看官老爷觉得还可以的话,您的评论和点赞都是对博主的肯定支持或者斧正!!!哈哈,博主会接着继续框架学习与研究系列....


更新(2017年9月26日00:36:19)

关于评论同学的问题,做了相应的回答和测试,本着我们打破砂锅问到底的精神,我们继续来测试研究一下IScheduler的API方法,其实大致看了一下API文档(2.x):http://quartznet.sourceforge.net/apidoc/2.0/html/html/edbcd9ad-0bf8-2b0e-52c9-e8a62ac4f610.htm 直接关键词搜索: IScheduler 即可,然后选择Method就可以看到很多API了找个还是比较好的且文档的注释其实就是最好的解释和说明了哈,就不用去github找了哈,这个文档也是自动生成的哦,多一句废话....然后什么暂停删除重启jobdetail、trigger都有说明的,这里博主总结一下就是:核心对象是jobdetail对象实例,trigger都是围绕它的,其次才是scheduler,然后jobdetail实质逻辑是IJob实例承载,所以这样一来对jobdetail的删除暂停都会影响到对应的trigger,但是一个jobdetail被多个trigger触发时,某个trigger暂停对jobdetail来说是透明不知道的,这点从一开始的组件分离设计也体现出来了,所以搞清楚了它们的关系也就自然明白了调度是谁与谁之间的相互关系、相互制约了哈,时间不早了,各位晚安!!!


后续补充(2017年9月27日00:34:17)

1、关于使用IScheduler的API:PauseJob 暂停任务之后,下次再重启 ResumeJob 的时候,中途的间隔时间差,将会由该 JobDetial 所从属的 Triggers 去补偿,前提是这些 Triggers 还有执行的机会,例如 RepeatForever计算自身的Interval时间间隔与JobDetail中断间隔的时间倍数自觉补偿丢失执行的次数 或者 RepeatCount 还有剩余次数的情况,将会消耗剩余次数去弥补JobDetail中断的时间差等(注意补偿的时刻是JobDetail一旦重启的那个时刻);同理还存在 PauseTrigger 暂定某个触发器之后,下次再启动 ResumeTrigger 该触发器的时候,中间间隔的时间差,如果该 Trigger 还有机会执行,将会去弥补丢失的次数,同样是举例:RepeatForever通过自身的Interval时间间隔与时间差比较然后执行需要弥补的次数 或者 RepeatCount 还有剩余次数的情况,将会消耗剩余次数去弥补时间差(注意补偿的时刻是该Trigger一旦重启的那个时刻)。以上所述呐,博主相继实验上述的情况情况属实,这个应该框架本身一种约定或者意识补偿机制,并且有一位园友也发现了提醒了博主,谢过......再次补充楼主再次想到 [DisallowConcurrentExecution] 可能会有影响,再次把上面的测试再来了一发,结果依然如此,那就是框架已经控制了,测试途中发现在补偿的时间点方面,基于 RAMJobStore 情况下,补偿激发时刻时间精度十分标准的让人难以置信,集中激发次数的时刻都几乎一致,博主这里测试的是 {DateTime.Now:yyyy-MM-dd HH:mm:ss.ffffff} ffffff 几乎一致,最后几位稍微有些差值,根据测试当时情况而定.....23333
毕个例子上述某种情况,测试看下图(注意红框部分既是激发时刻与补偿次数):

我去,时间又不早了,该休息了,每日一学,千里之行始于足下!!!


更新(2017年10月6日11:29:25)

1、关于使用winservice来作为定时器quartz.net的宿主程序,这里社区提供了Topshlf(https://github.com/Topshelf/Topshelf)以及对应quartz.net的扩展库Topshelf.Integrations(https://github.com/dtinteractive/Topshelf.Integrations/),至于用不用以及用不用得上那就看自己了,只是说这有些解决方案而已,再者TopShlf确实方便灵活包括部署编写和使用,读者可自行去wiki跟着看看大致有个了解,做到心中有数,使用winservice部署还可以一定程序上的解决了重启的问题,就不会像iis一样有回收需要重启定时器的问题,也算是定期器不较好的宿主方式了吧

2、那么在不同宿主的情况下,我们想通过UI界面观察和了解当前调度器的任务和触发器的情况,以及本身的大致情况有一个了解,该怎么办嗯,这里社区是万能的,哈哈我们只是代码的搬运工和使用方,提供了CrystalQuartz(具体看详情链接:https://github.com/guryanovev/CrystalQuartz)链接内容简直不能太详细了,具体可以看demo:https://github.com/guryanovev/CrystalQuartz/tree/master/examples 介绍了各种情况下面的解决方案,大体有下面几种方式:CrystalQuartz.Simple、CrystalQuartz.Remote、CrystalQuartz.Owin 三种方式,相信在实际运用过程中,应该有你想要使用的场景吧
关于Quartz.Net本身及其周边就说到这里,期间也更新了几次,哈哈,由于后面去了解周边才有所了解去看来wiki和一些教程,由此在这里再次记录一下以此备份或者加深印象。好的,接下来将会对一个框架及其周边衍生物做出如同的学习和探究.......


二度更新(2017年10月17日20:03:15)

Quartz.Net 作者已经更新了支持.net standrad2.0了,就意味着可以在.net core平台使用了,虽然发布的beta版本但是下载数量已经有了好几百,下载地址:https://www.nuget.org/packages/Quartz/3.0.0-beta1,更新说明地址:https://www.quartz-scheduler.net/2017/10/08/quartznet-3.0-beta1-released.html,相信不久之后就会发布release版本,届时.net core框架开发就不怕没有定时器的支持了,~( ̄▽ ̄~)(~ ̄▽ ̄)~

posted @ 2017-09-25 09:36  DJLNET  阅读(2428)  评论(14编辑  收藏  举报