提示

此常见问题解答改编自 Quartz Java

一般问题

什么是 Quartz ?

Quartz 是一个作业调度系统,可以与几乎任何其他软件系统集成或一起使用。 “作业调度程序”一词似乎为不同的人带来了不同的想法。当您阅读本教程时,您应该能够清楚地了解我们使用该术语时的含义,但简而言之,作业调度程序是一个系统,负责在预-确定的(预定的)时间到了。

Quartz 非常灵活,包含多个可以单独或一起使用的使用范例,以实现您想要的行为,并使您能够以对您的项目最“自然”的方式编写代码。

Quartz 非常轻巧,需要很少的设置/配置 - 如果您的需求相对基本,它实际上可以“开箱即用”使用。

Quartz 是容错的,并且可以在系统重新启动之间保留(“记住”)您计划的作业。

尽管 Quartz 对于在给定的时间表上简单地运行某些系统进程非常有用,但是当您学习如何使用它来驱动应用程序的业务流程流时,可以充分发挥 Quartz 的潜力。

什么是 Quartz - 从软件组件视图?

Quartz 作为一个小的动态链接库(.dll 文件)分发,其中包含所有核心 Quartz 功能。 此功能的主接口 (API) 是调度程序接口。 它提供了简单的操作,例如调度/取消调度作业,启动/停止/暂停调度程序。

如果您希望安排自己的软件组件执行,它们必须实现简单的 Job 接口,其中包含方法 execute()。 如果您希望在预定的触发时间到达时通知组件,那么组件应该实现 TriggerListener 或 JobListener 接口。

Quartz 的主“进程”可以在您自己的应用程序或独立应用程序(带有远程接口)中启动和运行。

为什么不直接使用 System.Timers.Timer ?

.NET Framework 通过 System.Timers.Timer 类具有“内置”定时器功能 - 为什么有人会使用 Quartz 而不是这些标准功能?

有很多原因! 这里有几个:

  • 定时器没有持久化机制。
  • 定时器具有不灵活的调度(只能设置开始时间和重复间隔,不能基于日期、时间等)。
  • 定时器不使用线程池(每个定时器一个线程)
  • 定时器没有真正的管理方案 —— 你必须编写自己的机制来记忆、组织和检索任务名称等。

…当然,对于一些简单的应用程序,这些功能可能并不重要,在这种情况下,不使用 Quartz.NET 可能是正确的决定。

其他问题

Quartz 可以运行多少个作业?

这是一个很难回答的问题…… 答案基本上是“视情况而定”。

我知道你讨厌这个答案,这里有一些关于它依赖什么的信息。

首先,您使用的 JobStore 起着重要作用。基于 RAM 的 JobStore 比基于 ADO.NET 的 JobStore 快得多(1000 倍)。 AdoJobStore 的速度几乎完全取决于连接到数据库的速度、您使用的数据库系统以及数据库运行的硬件。 Quartz 实际上自己做的处理很少,几乎所有的时间都花在了数据库上。当然,RAMJobStore 对可以存储的 Jobs & Triggers 的数量有更有限的限制,因为您肯定拥有比用于数据库的硬盘空间更少的 RAM。您还可以查看常见问题解答“如何提高 AdoJobStore 的性能?”

因此,Quartz 可以“存储”和监控的触发器和作业数量的限制因素实际上是 JobStore 可用的存储空间量(RAM 量或磁盘空间量)。

现在,除了“我可以存储多少?”问题是“Quartz 可以同时运行多少个作业?”

CAN 减慢 quartz 本身的一件事是使用大量侦听器(TriggerListeners、JobListeners 和 SchedulerListeners)。在每个侦听器中花费的时间显然会增加“处理”作业执行所花费的时间,而不是实际执行作业。这并不意味着你应该害怕使用监听器,它只是意味着你应该明智地使用它们——如果你真的可以制作更专业的监听器,就不要创建一堆“全局”监听器。也不要在听众中做“昂贵”的事情,除非你真的需要。还要注意许多插件(例如“历史”插件)实际上是侦听器。

可以在任何时候运行的实际作业数受线程池大小的限制。如果池中有五个线程,则一次最多可以运行五个作业。但是要小心创建大量线程,因为虚拟机、操作系统和 CPU 都很难同时处理大量线程,并且性能会因为所有的管理而下降。在大多数情况下,当您进入数百个线程时,性能开始下降。请注意,如果您在应用程序服务器中运行,它可能已经创建了至少几十个自己的线程!

除了这些因素之外,这实际上取决于你的工作是做什么的。如果您的工作需要很长时间才能完成工作,和/或他们的工作非常占用 CPU,那么您显然无法一次运行很多工作,也不能在给定的时间跨度内运行很多工作.

最后,如果您无法从一个 Quartz 实例中获得足够的马力,您总是可以对多个 Quartz 实例进行负载平衡(在不同的机器上)。每个人都将按照先到先得的原则从共享数据库中运行作业,只要触发器需要触发即可。

所以在这里你对“有多少”的答案已经这么远了,我还没有给你一个数字而且我真的很讨厌,因为上面提到的所有变量。所以我只想说,有一些分期的 Quartz Java 管理着数十万个作业和触发器,并且在任何给定的时间都在执行几十个作业——这不包括使用负载平衡。考虑到这一点,大多数人应该确信他们可以从 Quartz 中获得他们需要的性能。

关于 Job 的问题

如何控制 Job 的实例化?

请参阅 Quartz.Spi.IJobFactory 和 Quartz.IScheduler.JobFactory 属性。

如何防止作业在完成后被删除?

设置属性 JobDetail.Durable = true - 指示 Quartz 在 Job 成为“孤儿”时不要删除 Job(当 Job 不再有 Trigger 引用它时)。

如何防止作业同时触发?

Quartz.NET 2.x

实现 IJob 并使用 [DisallowConcurrentExecution] 属性装饰您的作业类。阅读 DisallowConcurrentExecutionAttribute 的 API 文档以获取更多信息。

Quartz.NET 1.x

使作业类实现 IStatefulJob 而不是 IJob。阅读 IStatefulJob 的 API 文档以获取更多信息。

如何停止当前正在执行的作业?

Quartz 1.x 和 2x:参见 Quartz.IInterruptableJob 接口和 IScheduler.Interrupt(string, string) 方法。

Quartz 3.x:见 IJobExecutionContextCancellationToken.IsCancellationRequested

关于触发器的问题

如何链接 Job 执行?或者,如何创建工作流?

目前没有“直接”或“免费”的方式来使用 Quartz 链接触发器。但是,您可以通过多种方式轻松完成它。以下是几种方法的概述:

一种方法是使用监听器(即 TriggerListener、JobListener 或 SchedulerListener),它可以注意到作业/触发器的完成,然后立即安排新的触发器触发。这种方法可能会涉及到一些问题,因为您必须通知侦听器执行哪个作业

  • 您可能需要担心这些信息的持久性。

另一种方法是构建一个 Job,在其 JobDataMap 中包含要触发的下一个作业的名称,并在作业完成时(其 Execute() 方法的最后一步)让作业安排下一个作业。有几个人正在这样做,并且运气不错。大多数人已经创建了一个基本(抽象)类,它是一个 Job,它知道如何使用特殊键(常量)从 JobDataMap 中获取作业名称和组,并包含用于安排已识别作业的代码。然后他们简单地扩展这个类,包括工作应该做的额外工作。

将来,Quartz 将提供一种更简洁的方法来执行此操作,但在此之前,您必须使用上述方法之一,或者考虑另一种更适合您的方法。

为什么我的触发器没有触发?

最常见的原因是没有调用 Scheduler.Start(),它告诉调度程序开始触发触发器。

第二个最常见的原因是触发器或触发器组已暂停。

夏令时和触发器

CronTrigger 和 SimpleTrigger 都以自己的方式处理夏令时 - 每种方式都以对触发器类型直观的方式。

首先,作为对夏令时的回顾,请阅读此资源:http://webexhibits.org/daylightsaving/g.html。有些读者可能不知道不同国家/内容的规则是不同的。例如,2005 年夏令时在美国从 4 月 3 日开始,而在埃及从 4 月 29 日开始。同样重要的是要知道,不仅不同当地人的日期不同,而且轮班时间也不同,因为好吧。许多地方在凌晨 2:00 换班,但其他地方在凌晨 1:00 换班,其他地方在凌晨 3:00 换班,还有一些在午夜换班。

SimpleTrigger 允许您安排作业每 N 毫秒触发一次。因此,为了“按计划进行”,它不必特别针对夏令时做任何事情——它只是每 N 毫秒保持一次触发。无论您的 SimpleTrigger 每 10 秒、每 15 分钟、每小时或每 24 小时触发一次,它都会继续这样做。然而,这让一些用户感到困惑的是,如果您的 SimpleTrigger 每 12 小时触发一次,那么在夏令时切换之前,它可能会在似乎是凌晨 3:00 和下午 3:00 时触发,但在夏令时 4 之后:上午 00 点和下午 4:00。这不是错误

  • 触发器每 N 毫秒精确地触发一次,只是人类施加在那个时刻的那个时间的“名称”发生了变化。

CronTrigger 允许您根据“公历”安排作业在特定时刻触发。因此,如果您创建一个触发器以在每天上午 10:00 触发,那么在夏令时切换之前和之后它将继续这样做。但是,根据是春季还是秋季夏令时事件,对于那个特定的周日,从周六上午 10:00 触发触发在周日上午 10:00 触发之间的实际时间间隔将是不是 24 小时,而是分别为 23 或 25 小时。

关于夏令时,用户还必须了解 CronTrigger 的另一点。这就是您应该仔细考虑创建在午夜和凌晨 3:00 之间触发的计划(关键的时间窗口取决于触发器的区域设置,如上所述)。原因是,根据触发器的时间表和特定的日光事件,触发器可能会被跳过,或者可能在一两个小时内似乎不会触发。例如,假设您在美国,夏令时事件发生在凌晨 2:00。如果您有一个每天凌晨 2:15 触发的 CronTrrigger,那么在夏令时开始的那一天,触发器将被跳过,因为当天凌晨 2:15 永远不会发生。如果您有一个 CronTrigger 每天每小时每 15 分钟触发一次,那么在夏令时结束的那一天,您将有一个小时的时间不会发生触发,因为当凌晨 2:00 到达时,它将变为 1又是凌晨 00 点,但是 1 点钟的所有触发都已经发生,并且触发器的下一次触发时间设置为凌晨 2:00

  • 因此在接下来的一小时内不会发生任何触发。

总之,所有这些都非常有意义,如果您牢记以下两条规则,应该很容易记住:

  • SimpleTrigger 总是每 N 秒触发一次,与一天中的时间无关。
  • CronTrigger 总是在一天中的给定时间触发,然后计算下一次触发。如果在给定日期没有出现该时间,则将跳过触发器。如果时间在给定的一天中出现两次,它只会触发一次,因为在第一次触发该时间后,它会计算一天中的下一次触发。

    关于 AdoJobStore 的问题

如何提高 AdoJobStore 的性能?

有几种已知的加速 AdoJobStore 的方法,其中只有一种非常实用。

首先,显而易见但不太实用:

  • 在运行 Quartz 的机器和运行 RDBMS 的机器之间购买更好(更快)的网络。
  • 购买更好(更强大)的机器来运行您的数据库。
  • 购买更好的 RDBMS。

其次,使用特定于您的数据库的驱动程序委托实现,如 SQLServerDelegate,以获得最佳性能。

提示

您还应该始终喜欢最新版本的库。 Quartz.NET 2.0 比 1.x 系列效率更高,并且 2.2.x 系列再次具有与早期 2.x 版本相比 AdoJobStore 相关的性能改进。

Web 环境中的 Quartz

当应用程序池被回收时,调度程序会一直停止

默认情况下,IIS 会不时回收和停止应用程序池。这意味着即使您在第一次访问 Web 应用程序时有 Application_Start 事件来启动 Quartz,调度程序也可能会由于站点不活动而在稍后被释放。

如果您有可用的 IIS 8,则可以将您的站点配置为预加载并保持运行。有关详细信息,请参阅此博客文章