Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
Quartz与夏/冬令时不得不说的故事,希望能够帮助你!!!。
通过cron表达式实现的定时任务会受到夏/冬令时切换影响导致本该处理的任务没有被触发,在查找是否有通用的解决方案来解决该问题时发现quartz官网的FAQ里有一段关于夏令时的回答。英语没有过六级的童鞋就不用去看了,我通过xx词霸研究了一下,并虚心请教了过了专八的老婆大人,得出以下结论:CronTrigger就是这么设计的,你在使用CronTrigger的时候就应该考虑到这种后果。
但是,先别激动,重点来了,quartz提供了另外一种方式来避免这个问题,那就是使用SimpleTrigger。为防止有人质疑我是不是真的看懂了,截图为证:
什么是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在计算下一次触发时间时,先计算出第一次触发时间的时间戳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);
夏令时切换冬令时对SimpleTrigger没有影响。
冬令时切换夏令时对SimpleTrigger也没有影响。
将触发间隔改为1小时同样不受影响。
CronTrigger计算下一次触发时间是通过解析Cron表达式得到的,而Cron表达式的计算是基于GregorianCalendar实现的。Gregorian,来自英伦岛屿的格里高利合唱团,又名“教皇合唱团”,由10位沉浸于教堂音乐与和声风格,拥有深厚古典基础的演唱家组成,桥豆麻袋,这和GregorianCalendar有什么关系?
答案是,
没有任何关系。。。。
任何关系。。。。
关系。。。。
主要是这块的逻辑非常复杂,代码很多,我由于时间有限(好吧,我承认是水平有限),这块逻辑我没有看懂。不过,虽然我没有看懂代码,但是我掌握了精髓,那就是夏令时切换冬令时当天,计算出来的下一次触发时间如果是1点,那肯定是冬令时的1点;冬令时切换夏令时当天,计算出来的下一次触发时间如果是2点,那么肯定是第二天的2点,因为当天没有2点。
DailyTimeIntervalTrigger和SimpleTrigger类似,可以指定每天的某个时间段内,以一定的时间间隔执行任务,可以指定执行的星期,但是最小只能到秒级。
来看看它是怎么计算下一次触发时间的
从代码来看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);
可以看出夏令时切换冬令时对DailyTimeIntervalTrigger没有影响。
也没有影响。
将触发间隔改为1小时也不受影响。
CalendarIntervalTrigger和DailyTimeIntervalTrigger功能十分相似,也是指定从某一个时间开始,以一定的时间间隔执行的任务,但是最大可以支持到年。它们的计算下一次触发时间的逻辑也十分相似。
当间隔是秒/分钟/小时时,计算逻辑和DailyTimeIntervalTrigger一致的。当间隔为天,周时,由于夏令时/闰年等影响,间隔时间转化成秒后不像分钟,小时级的一样是固定的,比如一小时是60*60秒,所以不能像之前一样根据已触发次数和触发间隔周期一次计算出目标时间,但是当间隔较大时一次一次叠加间隔周期性能又太差,这里quartz做了优化,根据已触发次数先叠加一个大致的时间,然后再在这个基础上一次一次叠加,直到得到下次触发时间。下面是以天为间隔的代码逻辑。
注意这个方法daylightSavingHourShiftOccurredAndAdvanceNeeded。
这里涉及到两个配置: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来避免夏/冬令时切换带来的影响。
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章