【问题标题】:Quartz.net - Issues with Adjusting and Speeding up SystemTime causing MisfiresQuartz.net - 调整和加速 SystemTime 导致失火的问题
【发布时间】:2015-04-21 21:10:33
【问题描述】:

出于测试原因,我希望能够调整 Quartz.Net 当前认为的时间,因此我不必等待数小时、数天或数周来检查我的代码是否正常工作。

为此,我创建了以下简单函数(它在 F# 中,但可以很容易地用 C# 或其他语言完成):

let SimulateTime = fun () ->
    currentTime <- DateTimeOffset.UtcNow
    timeDifferenceInSeconds <- (currentTime - lastCheckedTime).TotalSeconds
    simulatedTime <- simulatedTime.AddSeconds((timeDifferenceInSeconds *scaleTimeBy))
    lastCheckedTime <- currentTime
    simulatedTime

currentTime、lastCheckedTime 和simulatedTime 都是DateTimeOffset 类型,而timeDifferenceInSeconds 和scaleTimeBy 都是float 类型。

然后我将 SystemTime.Now 和 SystemTime.UtcNow 更改为使用上述功能,如下所示:

SystemTime.Now <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())
SystemTime.UtcNow <- 
    Func<DateTimeOffset>(
        fun () -> SimulateTime())

Mark Seemann 在我的上一个问题中展示了这一点,可以找到here

现在这主要是可行的,只是似乎较长的函数导致它偏离了相当大的幅度。我的意思是我所有的触发器都会失火。例如,如果我将触发器设置为每小时发生一次,并将 scaleTimeBy 设置为 60.0,以便通过的每一秒都计为一分钟,那么它永远不会真正按时触发。如果我有一个失火策略,那么触发器可以关闭,但它列出的激活时间将迟至半小时标记(因此比本示例中应该慢了整整 30 秒)。

但是我可以这样做:

Console.WriteLine(SimulateTime())
Thread.Sleep(TimeSpan.FromSeconds(60.0))
Console.WriteLine(SimulateTime())

在这个例子中,两次输出到屏幕的时间差正好是一个小时,所以调用看起来不应该比它增加那么多的时间差。

有人对如何解决此问题或处理此问题的更好方法有任何建议吗?

编辑: 所以 SimulateTime 函数的 C# 版本应该是这样的:

public DateTimeOffset SimulateTime() {
    currentTime = DateTimeOffset.UtcNow;
    double timeDifference = (currentTime - lastCheckedTime).TotalSeconds;
    simulatedTime = simulatedTime.AddSeconds(timeDifference * scaleTimeBy);
    lastCheckedTime = currentTime
    return simulatedTime;}

如果这有助于任何人解决这个问题。

【问题讨论】:

  • 虽然我现在对 Quartz.Net 一无所知,但这听起来像是 Quartz.Net 的问题,或者你使用它的方式。我猜想熟悉这个库的 C# 开发人员比 F# 开发人员要多得多,因此如果您在 C# 中重现该问题,并在此处发布 C# 代码而不是 F#...
  • 以上是不是更有帮助?
  • 还决定看看是否可以通过在调用模拟时间函数时将 timeDifference 与当前时间一起打印来解决问题。看起来 Quartz.net 通常会同时进行 5 次调用,然后在进行另一组调用之前等待。在测试时,这个等待时间似乎在大约 20 秒和大约 0.001 秒之间交替(主要问题中提到的 30 秒似乎更像是一个不寻常的异常值,尽管 20 秒仍然很糟糕),有人知道这是为什么吗?
  • 因此,通过挖掘 Quartz.net 的源代码,我在 SchedulerThread 中发现了一个名为 idleWaitTime 的变量,我猜它是负责的。有没有人能够确认或过去处理过它?
  • 是的,它似乎与 idleWaitTime 有关,而且这个问题似乎通常只在与服务器打交道时出现。它可能是可配置的,我现在正试图弄清楚如何做到这一点。如果有人确实知道如何更改它(最好以一种可以根据 ScaleTimeBy 动态更改它的方式)让我知道,因为它会为我节省一些工作。

标签: c# f# quartz.net c#-to-f# quartz.net-2.0


【解决方案1】:

所以这个问题是由于 Quartz.net 将空闲并等待它认为它不会很快发生任何触发器以避免进行太多调用而导致的失败。默认情况下,如果在时间跨度内没有发生任何触发器,它会等待大约 30 秒。 idleWaitTime 变量是在 QuartzSchedulerThread 中设置的时间跨度。现在,在检查可能很快发生的触发器时,它还使用 QuartzSchedulerResources 中的 BatchTimeWIndow。

idleWaitTime 和 BatchTimeWindow 都可以在配置/属性文件中设置,它们被称为“org.quartz.scheduler.idleWaitTime”和“org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow”。

基于它在 BatchTimeWindow 中调用的内容,我认为这只是为了获取一个变量而向前看(如果我加快速度,我想要一个小的 idleWaitTime 但我想要它进一步向前看触发器,因为你等待的几秒钟实际上是几分钟,所以会比它想象的更早触发),但是在配置属性的页面上对“org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow”的描述意味着它可能导致事情早点开火,不太准确。所以从这里开始就是修改idleWaitTime的代码

let threadpool = Quartz.Simpl.SimpleThreadPool()
let jobstore = Quartz.Simpl.RAMJobStore()
let idleWaitTime = TimeSpan.FromSeconds(30.0/scaleTimeBy)
let dbfailureretryinverval = TimeSpan(int64 15000)
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,jobstore,idleWaitTime,dbfailureretryinverval)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

您可以使用 DirectSchedulerFactory 创建一个具有所需 idleWaitTime 的调度程序,这可能会使用更好的文档。它还需要一堆你可能想或不想修改的东西,具体取决于你正在做的事情。对于线程池,我只使用 Quartz.net 的默认 SimpleThreadPool,因为此时我不在乎弄乱线程,也不想解释你是如何这样做的,除非这是问题的全部重点。可通过here 获取有关工作商店的信息。我在这里使用 RAMJobStore 是因为它比 AdoJobStore 更简单,但对于这个例子来说并不重要。 dbfailureretryinterval 是本示例不关心的另一个值,因此我只是查找了默认设置的值。对于这个例子,它的值应该是最不重要的,因为它没有连接到数据库。对于 idleWaitTime 可能想要做更多的测试来确定什么是好的值,但我选择通过 scaleTimeBy 缩放其默认值 30 秒,因为这是我用来缩放事情进展速度的方法经过。因此,如果我让程序模拟时间以更快的速度流逝,那么这应该可以做到,那么它应该只在更短的时间内保持空闲状态。需要注意的重要一点是,当以这种方式创建调度程序时,它也不会返回,因此需要单独调用以获取我刚刚创建的调度程序。我不知道为什么会这样,我猜如果您要创建多个调度程序并且不一定要使用所有调度程序,那么这种方式会更好。

毕竟现在你可能仍然会遇到一些失火率。虽然它现在空闲的时间单位要小得多(只有几秒钟,因此可能是可接受的余量,具体取决于您的用例),但它仍然存在问题,即它只是检查它是否有即将到来的触发器接下来的几分之一秒。

那么让我们看看为 BatchTimeWindow 添加时间是否有帮助?

let threadpool = Quartz.Simpl.SimpleThreadPool()
let threadexecutor = Quartz.Impl.DefaultThreadExecutor()
let jobstore = Quartz.Simpl.RAMJobStore()
let schedulepluginmap = System.Collections.Generic.Dictionary<String,Quartz.Spi.ISchedulerPlugin>()
let idleWaitTime = TimeSpan.FromSeconds(30.0/timeScale)
let maxBatchSize = 1
let batchTimeWindow = TimeSpan.FromSeconds(timeScale)
let scheduleexporter = Quartz.Simpl.RemotingSchedulerExporter()
Quartz.Impl.DirectSchedulerFactory.Instance.CreateScheduler("TestScheduler","TestInstance",threadpool,threadexecutor,jobstore,schedulepluginmap,idleWaitTime,maxBatchSize,batchTimeWindow,scheduleexporter)
let scheduler = Quartz.Impl.DirectSchedulerFactory.Instance.GetScheduler("TestScheduler")

现在这有更多的变量,这些变量在本示例中并不真正关心,甚至不会费心复习,因为调整 batchTimeWindow 实际上会使情况变得更糟。就像让你在 30 分钟前回到失火状态。所以不,batchTimeWindow 虽然看起来可能有用,但不是。只修改 idleWaitTime。

理想情况下,此用途需要较短的等待时间和较长的预读时间,但该选项似乎不可用。

【讨论】:

  • 所以这是我能够弄清楚的,我还没有将其标记为我接受的答案,以防万一有更好的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-05
  • 2014-09-14
  • 1970-01-01
相关资源
最近更新 更多