Quartz与夏/冬令时不得不说的故事

(1) 2024-10-02 20:23

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
Quartz与夏/冬令时不得不说的故事,希望能够帮助你!!!。

通过cron表达式实现的定时任务会受到夏/冬令时切换影响导致本该处理的任务没有被触发,在查找是否有通用的解决方案来解决该问题时发现quartz官网的FAQ里有一段关于夏令时的回答。英语没有过六级的童鞋就不用去看了,我通过xx词霸研究了一下,并虚心请教了过了专八的老婆大人,得出以下结论:CronTrigger就是这么设计的,你在使用CronTrigger的时候就应该考虑到这种后果。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第1张

但是,先别激动,重点来了,quartz提供了另外一种方式来避免这个问题,那就是使用SimpleTrigger。为防止有人质疑我是不是真的看懂了,截图为证:

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第2张

什么是SimpleTrigger?SimpleTrigger和CronTrigger有什么区别?SimpleTrigger是quartz提供的一种简单触发器,可以使用这个触发器实现简单的以固定时间间隔执行的任务,可以到ms级别,比如每隔10ms执行一次。从第一次触发开始,SimpleTrigger会一直严格地按照设置的时间间隔来执行,而不关心当前是几点。CronTrigger可以通过cron表达式来设置复杂的触发条件,与SimpleTrigger不同的是,CronTrigger关注的不是时间间隔,而是通过cron表达式解析出来的具体时间,比如表达式'0 0 2 * * * ?'解析出来的是每天凌晨2点执行任务,那么仅当且当到达2点时这个任务才会被执行。 有人要问了,这有什么问题?2点不是每天都有吗?NO,冬令时切换夏令时的当天是没有2点的,1点结束后直接就是3点,是不是很神奇,所以使用CronTrigger实现的定时任务遇到了夏/冬令时切换,很可能会出现预期外的状况。

上一篇文章分析过quartz的工作原理,当quartz把一个任务加载到内存的时候就会计算出它的下一次触发时间,到指定时间后就会执行任务,下面我们就来看看SimpleTrigger和CronTrigger是如何计算下一次触发时间的。

SimpleTrigger

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第3张

通过代码可以看出,SimpleTrigger在计算下一次触发时间时,先计算出第一次触发时间的时间戳startMillis,然后再根据当前时间的时间戳和触发间隔时间计算出已经触发任务的次数numberOfTimesExcuted,最后计算出下一次要触发的时间,所有关于时间的计算都是基于时间戳。

以一个demo为例测试一下夏/冬令时对SimpleTrigger的影响,每隔5s触发一次任务。

Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, Scheduler.DEFAULT_GROUP) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(10)) .startAt(calendar.getTime()).build(); scheduler.scheduleJob(jobDetail, trigger);

夏令时切换冬令时

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第4张

夏令时切换冬令时对SimpleTrigger没有影响。

冬令时切换夏令时

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第5张

冬令时切换夏令时对SimpleTrigger也没有影响。

将触发间隔改为1小时同样不受影响。

CronTrigger

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第6张

CronTrigger计算下一次触发时间是通过解析Cron表达式得到的,而Cron表达式的计算是基于GregorianCalendar实现的。Gregorian,来自英伦岛屿的格里高利合唱团,又名“教皇合唱团”,由10位沉浸于教堂音乐与和声风格,拥有深厚古典基础的演唱家组成,桥豆麻袋,这和GregorianCalendar有什么关系?

答案是,

没有任何关系。。。。

任何关系。。。。

关系。。。。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第7张

主要是这块的逻辑非常复杂,代码很多,我由于时间有限(好吧,我承认是水平有限),这块逻辑我没有看懂。不过,虽然我没有看懂代码,但是我掌握了精髓,那就是夏令时切换冬令时当天,计算出来的下一次触发时间如果是1点,那肯定是冬令时的1点;冬令时切换夏令时当天,计算出来的下一次触发时间如果是2点,那么肯定是第二天的2点,因为当天没有2点。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第8张

DailyTimeIntervalTrigger

DailyTimeIntervalTrigger和SimpleTrigger类似,可以指定每天的某个时间段内,以一定的时间间隔执行任务,可以指定执行的星期,但是最小只能到秒级。

来看看它是怎么计算下一次触发时间的

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第9张

从代码来看DailyTimeIntervalTrigger关注的也是interval,与SimpleTrigger不同的是,它是通过Calendar来计算时间的。注意这里的变量sTime,当设置了起始时间时为起始时间;没有设置时是每一天的最开始时间,即0点0分0秒。下一次触发时间都是基于sTime这个时间来计算的。

来跑个demo,5s触发一次任务。

Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, Scheduler.DEFAULT_GROUP) .withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule().withIntervalInSeconds(5).withRepeatCount(10)) .startAt(calendar.getTime()).build(); scheduler.scheduleJob(jobDetail, trigger);

夏令时切换冬令时

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第10张

可以看出夏令时切换冬令时对DailyTimeIntervalTrigger没有影响。

冬令时切换夏令时

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第11张

也没有影响。

将触发间隔改为1小时也不受影响。

CalendarIntervalTrigger

CalendarIntervalTrigger和DailyTimeIntervalTrigger功能十分相似,也是指定从某一个时间开始,以一定的时间间隔执行的任务,但是最大可以支持到年。它们的计算下一次触发时间的逻辑也十分相似。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第12张

当间隔是秒/分钟/小时时,计算逻辑和DailyTimeIntervalTrigger一致的。当间隔为天,周时,由于夏令时/闰年等影响,间隔时间转化成秒后不像分钟,小时级的一样是固定的,比如一小时是60*60秒,所以不能像之前一样根据已触发次数和触发间隔周期一次计算出目标时间,但是当间隔较大时一次一次叠加间隔周期性能又太差,这里quartz做了优化,根据已触发次数先叠加一个大致的时间,然后再在这个基础上一次一次叠加,直到得到下次触发时间。下面是以天为间隔的代码逻辑。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第13张

注意这个方法daylightSavingHourShiftOccurredAndAdvanceNeeded。

Quartz与夏/冬令时不得不说的故事_https://bianchenghao6.com/blog__第14张

这里涉及到两个配置:preserveHourOfDayAcrossDaylightSavings和skipDayIfHourDoesNotExist,分别表示是否保持夏令时时间的24小时制的小时数和当小时不存在时是否跳过这一天。

以一个2018/3/10 2:43:22触发的间隔为1天的任务来作为demo:

当preserveHourOfDayAcrossDaylightSavings和skipDayIfHourDoesNotExist均为false时,计算出的下一次触发时间是2018/3/11 1:43:22。

当preserveHourOfDayAcrossDaylightSavings为true,skipDayIfHourDoesNotExist为false时,计算出的下一次触发时间是2018/3/11 3:43:22。

当preserveHourOfDayAcrossDaylightSavings为true,skipDayIfHourDoesNotExist为true时,计算出的下一次触发时间是2018/3/12 2:43:22。

夏令时切换冬令时

不受影响。

冬令时切换夏令时

当间隔的时间单位大于等于天,并且预期时间是2点时会收到影响,其余不受影响。

基于以上分析,当任务的触发条件比较简单时,可以使用其它几种trigger代替CronTrigger来避免夏/冬令时切换带来的影响。

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复