本文档改编自 Quartz Java

JobDataMap 小技巧

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

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

使用合并的JobDataMap

在 Job 执行期间在 JobExecutionContext 上找到的 JobDataMap 起到了方便的作用。 它是 JobDetail 上的 JobDataMap 和 Trigger 上的 JobDataMap 的合并,后者中的值覆盖前者中的任何同名值。

如果您有一个作业存储在调度程序中以供多个触发器定期/重复使用,但对于每个独立的触发,您希望为作业提供不同的数据输入,则在触发器上存储 JobDataMap 值可能很有用。

鉴于上述所有情况,我们推荐以下最佳实践: IJob.Execute(..) 方法中的代码通常应从 JobData 检索值

Trigger 小技巧

使用 TriggerUtils

TriggerUtils:

  • 提供一种创建日期的简单方法(用于开始/结束日期)
  • 提供分析触发器的助手(例如计算未来的触发时间)

ADO.NET 作业存储

永远不要直接写入 Quartz 的表

将调度数据直接写入数据库(通过 SQL)而不是使用调度 API:

  • 导致数据损坏(已删除数据、加扰数据)
  • 当触发器的触发时间到达时,导致作业看似“消失”而不执行
  • 当触发器的触发时间到达时,导致作业不执行“只是坐在那里”
  • 可能导致:死锁
  • 其他奇怪的问题和数据损坏

切勿将非集群调度程序与具有相同调度程序名称的另一个调度程序指向同一数据库

如果您将多个调度程序实例指向同一组数据库表,并且其中一个或多个实例未配置为集群,则可能会发生以下任何情况:

  • 导致数据损坏(已删除数据、加扰数据)
  • 当触发器的触发时间到达时,导致作业看似“消失”而不执行
  • 当触发器的触发时间到达时,导致作业未执行,“只是坐在那里”
  • 可能导致:死锁
  • 其他奇怪的问题和数据损坏

确保足够的数据源连接大小

建议您的 Datasource 最大连接大小至少配置为线程池中的工作线程数加 3。如果您的应用程序还频繁调用调度程序 API,您可能需要额外的连接。

夏令时

避免在夏令时转换时间附近安排作业

注意:转换时间的细节和时钟向前或向后移动的时间量因地区而异,请参阅:https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world

SimpleTriggers 不受夏令时的影响,因为它们总是以精确的毫秒时间触发,并重复精确的毫秒数。

因为 CronTriggers 在给定的小时/分钟/秒触发,所以当 DST 转换发生时它们会受到一些奇怪的影响。

作为可能问题的示例,在美国的时区/位置中安排遵守夏令时,如果在凌晨 1:00 和凌晨 2:00 使用 CronTrigger 并安排开火时间,可能会出现以下问题:

  • 凌晨 1:05 可能会出现两次! - 可能在 CronTrigger 上重复触发
  • 凌晨 2:05 可能永远不会发生! - 可能错过 CronTrigger 上的触发

同样,调整的具体时间和数量因地区而异。

基于沿日历滑动(而不是精确的时间量)的其他触发器类型,例如 CalenderIntervalTrigger,将受到类似影响 - 但不是错过一次触发或触发两次,最终可能会使其触发时间偏移一个小时。

Job

等待条件

长时间运行的作业会阻止其他作业运行(如果 ThreadPool 中的所有线程都忙)。

如果您觉得需要在执行作业的工作线程上调用 Thread.sleep(),这通常表明作业尚未准备好完成其余工作,因为它需要等待某些条件(例如数据记录的可用性)成为真实。

更好的解决方案是释放工作线程(退出作业)并允许其他作业在该线程上执行。该作业可以在退出之前重新安排自己或其他作业。

抛出异常

Job 的执行方法应该包含一个处理所有可能异常的 try-catch 块。

如果作业抛出异常,Quartz 通常会立即重新执行它,这意味着该作业可以并且很可能会再次抛出相同的异常。这可能会导致资源浪费,在最坏的情况下,还会导致应用程序不稳定或崩溃。如果作业捕获它可能遇到的所有异常、处理它们并重新安排自己或其他作业以解决该问题,那就更好了。

可恢复性和幂等性

在调度程序失败后,标记为“可恢复”的进行中作业会自动重新执行。这意味着某些作业的“工作”将被执行两次。

这意味着作业应该以使其工作是幂等的方式编码。

监听器(TriggerListener、JobListener、SchedulerListener)

保持监听器中的代码简洁高效

不鼓励执行大量工作,因为将执行作业(或完成触发器并继续触发另一个作业等)的线程将被绑定在侦听器中。

处理异常

每个侦听器方法都应包含一个处理所有可能异常的 try-catch 块。

如果某个监听器抛出异常,可能会导致其他监听器无法收到通知和/或阻止作业的执行等。

通过应用程序公开调度程序功能

注意安全!

一些用户通过应用程序用户界面公开 Quartz 的调度程序功能。这可能非常有用,尽管它也可能非常危险。

确保您不会错误地允许用户使用他们想要的任何参数来定义他们想要的任何类型的作业。例如,Quartz.Jobs 包附带一个预制作业 NativeJob,它将执行它定义的任意本机(操作系统)系统命令。恶意用户可以使用它来控制或破坏您的系统。

同样,其他作业(例如 SendEmailJob)以及几乎任何其他作业都可能被用于恶意目的。

允许用户定义他们想要的任何工作,从而有效地打开您的系统,从而使您的系统容易受到与 OWASP 和 MITRE 定义的命令注入攻击相当/等效的各种漏洞的攻击。