Quartz.NET 的 Misfire 策略,我踩过的三个坑
调度任务挂了半天再恢复,Quartz 会怎么处理错过的那几次触发?这就是 misfire 问题。第一次遇到的时候,我以为配置了 WithMisfireHandlingInstructionDoNothing 就万事大吉,结果线上还是出现了连环补跑,数据库被同一个任务瞬间打了三遍。
后来翻源码才搞明白,misfire 判定发生在 Trigger 被调度器重新获取的那一刻,而不是任务恢复那一刻。也就是说,判定窗口取决于 misfireThreshold,默认 60 秒。停机时间超过这个窗口的触发,才会进入 misfire 处理流程。
三个坑分别是:
坑一:Cron 触发器的 WithMisfireHandlingInstructionDoNothing 只对"单次错过"生效。如果停机跨越了多个触发点,Cron 触发器只会补跑一次(FireOnceNow 语义)或者一次都不跑,但绝不会把中间每一次都补齐。想补齐要自己扫描业务表做补偿,别指望调度器。
坑二:集群模式下,misfire 处理由拿到 STATE_ACCESS 锁的节点执行,节点之间时钟偏差超过阈值时会出现"一台认为 misfire 了、另一台认为没有"的薛定谔状态。所有节点上 NTP 是硬要求。
坑三:misfireThreshold 调小可以让"轻微延迟"不触发补跑,但调得太小(比如 1 秒),正常 GC 停顿都会被误判。我的经验值是业务容忍度的一半:任务允许迟到 10 分钟,阈值就给 5 分钟。
var trigger = TriggerBuilder.Create()
.WithCronSchedule("0 0/30 * * * ?", x => x
.WithMisfireHandlingInstructionDoNothing()
.WithMisfireThreshold(TimeSpan.FromMinutes(5)))
.Build();
最后记住一句话:misfire 策略只是"错过的触发怎么处置",不是"业务补偿机制"。补偿要在业务层自己做,而且要做幂等。