首页 > 代码库 > Quartz最佳实践

Quartz最佳实践

本文来自对http://www.quartz-scheduler.org/documentation/best-practices.html的翻译。

表示还没用过Quartz,正准备用的,然后在官网上看到了这个,然后觉得还挺短的,就翻译一下。

 

最佳实践

        对生产环境中使用Tips

        任务数据映射Tips

        触发Tips

        JDBC的JobStore

        daylight savings time(这个翻译是夏令时,所以我不知道怎么翻译了)

        任务

        监听器(触发监听器,任务监听器,调度监听器)

        通过应用暴露调度器功能

 

生产环境中的建议

 

跳过更新检查

 

Quartz在连接服务的时候包含了一个“更新检查”的特性,这个特性会检查是否有Quartz的新版本可用。

这个个检查是异步进行的,并不会影响Quartz的启动和初始化时间,如果连接失败的话,它会优雅地结束。

如果运行了检查,而且找到了新的版本,将会在Quartz的日志中进行记录。

 

你可以将Quartz配置属性设置org.quartz.scheduler.skipUpdateCheck: true或者将系统属性org.terracotta.quartz.skipUpdateCheck=true(这个是在系统环境或者在java命令行中以-D加入),这样就可以禁用掉Quartz的更新检查。建议你在生产环境的部署中禁用更新检查。

 

任务数据映射的建议

 

在JobDataMap中仅仅存储原始数据类型(包括Strings)

 

在JobDataMap中仅仅存储原始数据类型(包括Strings)可以避免短期或长期的序列化的问题。

 

使用合并的JobDataMap

 

在任务执行的时候,JobExecutionContext中的JobDataMap作为一个convenience。它是通过在JobDetail中的JobDataMap和Trigger中的JobDataMap合并而来,后者中的值会覆盖前面一个中同名变量的值。

 

当你有一个任务在scheduler中,而且这个任务又会被多个Triggers重复使用,那么你最好把值存在Trigger的JobDataMap中,这样对于每次独立的任务触发时,你就可以为Job提供不同的数据输入啦。

 

根据以上所述,我们提出了如下的最佳实践:在调用Job.execute(...)方法时,一般来说应该从JobExecutionContext中的JobDataMap中解析变量的值,而不是直接从JobDetail的JobDataMap中解析。

 

触发器建议

 

使用TriggerUtils

TriggerUtils:

        提供了一个简单的方法来创建triggers(schedules)

        有很多不同的方法通过schedules来创建triggers以满足特定的描述,这个要比直接实例化特定类型的triggers(SimpleTrigger,CronTrigger等)然后调用不同的setter方法来配置它们方便许多

        提供了一个简单的方法来创建日期(比如start/end日期)

        提供了分析triggers的助手(比如计算剩余的触发时间)

 

JDBC JobStore

 

永远都不要直接往Quartz的表中写数据

 

通过SQL直接往数据库中写入scheduling数据而不是通过API会造成一下问题:

        会造成数据腐化(被删除的数据,混乱的数据)

        会造成任务在到达执行点的时候像没有执行就消失了

        会造成当触发时间到来时,而任务还未执行

        可能会造成死锁

        其他奇怪的问题和数据腐化

 

永远不要在同一个数据库中将一个非集群的调度器指向另一个相同名字的调度器

 

如果你在同一套数据库表中指定了多于一个的调度器实例,并且这些实例并不是配置在集群中,那么下面的情况将有可能会发生:

        会造成数据腐化(被删除的数据,混乱的数据)

        会造成任务在到达执行点的时候像没有执行就消失了

        会造成当触发时间到来时,而任务还未执行

        可能会造成死锁

        其他奇怪的问题和数据腐化

 

确保足够的数据源中的连接数量

 

建议将你的数据源连接数配置为配置为线程池中工作线程数加3。如果你的应用还要经常调用scheduler的API,那么你还需要增加额外的连接数量。如果你正在使用JobStoreCMT,那么“未管理的”数据源的最大连接数至少为4。

 

夏令时

 

避免将任务安排在接近夏令时的转移时间

 

注意:本地的时钟向前或者向后转移时和总的时间的细节可以在如下链接中找到:

https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world.

 

SimpleTriggers不受夏令时的影响,这是因为它们总是在毫秒时刻被精确地触发,并且在进过了精确的毫秒数之后会再次被触发。

 

由于CronTriggers会在给定的时/分/秒被触发,当夏令时转移时到来的时候,它们会受到这些怪事的影响。

 

举一个可能发生的例子,在夏令时的美国时区/位置进行调度的时候,如果使用CronTrigger并且调度的触发时间是在1:00 AM和2:00 AM之间时会发生下列的问题:

 

        1:05 AM也许会发生两次!可能会重复地触发CronTrigger

        2:05 AM也许永远不会发生!可能会遗漏CronTrigger的触发

 

同时,时间和调整量要根据当地位置来调节。

 

其他的触发器类型是根据日历的移动而不是根据确切的时间量来进行的,例如CalenderIntervalTrigger,将会同样地受影响,但不是错过触发或者触发两次,而是将它的触发时间偏移一个小时。

 

Jobs

 

等待条件来到

 

长时间运行的任务会阻止其他任务的运行(如果在线程池中所有的线程都繁忙)。

 

如果你认为需要调用Thread.sleep()这个方法来停止工作线程执行任务,这是一个典型的信号,任务不会完成其余的任务,因它必须等待某些条件的到来(比如某些数据可读)。

 

一个更好的方法是释放线程(退出任务)并且允许其他任务在这个线程执行。任务可以重新调度自己,或者在它退出之前其他任务。

 

抛出异常

 

一个任务的执行方法应该包含在try-catch块中,以此处理可能发生的异常。

 

如果一个任务抛出一个异常,Quartz一般会马上再执行它(可能会抛出相同的异常)。最好是任务捕获所有它可能遇到的异常并处理它们,然后重新调度自己或其他的任务。

 

可恢复性和幂等性

 

带有"recoverable"的任务会在调度器失败时重新执行。这意味着某些任务的工作将会被执行两次。

 

这意味着在编写任务的时候它的工作应该是幂等的。

 

监听器(TriggerListener,JobListener,SchedulerListener)

 

保持编写简洁高效的监听器

 

 

不建议在监听器中完成大量的工作,因为将要执行任务的线程(或者完成触发和引发另一个任务等)将会绑定在监听器上。

 

处理异常

 

每个监听器的方法都应该在try-catch块中处理所有可能的异常。

 

如果一个监听器抛出了一个异常,可能会造成其他的监听器无法被通知到或阻止其他任务的执行等。

 

通过应用来暴露调度器的功能

 

小心安全问题!

 

有的用户通过应用程序接口来暴露Quartz的调度功能。这会非常有用,虽它可能会造成极度的危险。

 

确保你没有错误地允许用户定义他们想要的任何参数和任何类型的任务。例如,Quartz会带有一个预定的任务org.quartz.jobs.NativeJob,这个任务将会在它们定义的任意的本地系统上执行命令。恶意的用户可能会使用这个来控制或者摧毁你的系统。

 

同样的像SendEmailJob之类的任务,并且事实上任何其他的任务都可以被当作恶意用途。

 

如果允许用户定义任意他们想要的任务将会是你的系统遭受各种可能的危害,等同于OWASP和MITRE定义的命令注入攻击等。

Quartz最佳实践