无论是使用Linux自带crontab程序,还是使用cron-utils的crontab语法解析,都存在整点运行问题。

问题来源

使用crontab语法的时候,如果是固定间隔运行,如

0  0/5  * * * *
30 */30 * * * *

其中分钟间隔只能是60的因子,如果不是60的因子,那么存在整点运行问题。如运行计划为

10 0/29 * * * *

那么运行时刻可能为

Mon Oct 09 15:29:10 CST 2017
Mon Oct 09 15:58:10 CST 2017
Mon Oct 09 16:00:10 CST 2017
...

这里的运行计划,多出来一个16:00:10,这是不在我们的运行计划中的。

解决问题

作者使用的是cron-utils,所以可以使用程序控制,实现规避整点运行的问题。

计算crontab时间间隔

代码先上:

/**
 * @DESC caluate the max seperates
 */
public static long calcMaxSep(ExecutionTime executionTime,
		DateTime updateCurrentFireTime) {

	DateTime nextFireTime = executionTime
			.nextExecution(updateCurrentFireTime);
	DateTime nextFireTime1 = executionTime.nextExecution(nextFireTime);

	long b1 = nextFireTime.toDate().getTime()
			- updateCurrentFireTime.toDate().getTime();
	long b2 = nextFireTime1.toDate().getTime()
			- updateCurrentFireTime.toDate().getTime();
	b2 = b2 / 2;

	long b3 = nextFireTime1.toDate().getTime()
			- nextFireTime.toDate().getTime();

	return getMaxByTri(b1, b2, b3);

}

/**
 * @DESC get max of input three numbers
 */
public static long getMaxByTri(long b1, long b2, long b3) {
	// return b1 > b2 ? (b1 > b3 ? b1 : b3) : (b2 > b3 ? b2 : b3);
	return Math.max(b1, Math.max(b2, b3));
}

  基本原理:三个运行时刻,不可能都存在整点运行问题,除非设置就是整点运行的。分析三个运行时间之间的时间间隔,如果是正常区间,那三个时间应该是一致的;如果区间内或边界存在整点问题,那么应该可以通过该方法解决。作者通过该方法获取运行时间间隔,没有发现问题。

通过间隔获取下一个运行时刻

public static void updateTask(SchedulerTask task) {
	task.setCurrentFiredTime(task.getNextFiredTime());// 直接获取计算好的下次执行时间,不再重新计算
	DateTime updateCurrentFireTime = new DateTime(task.getNextFiredTime());
	DateTime nextFireTime = nextFireTime(updateCurrentFireTime,Long.parseLong(task.getDescribe()));
	task.setNextFiredTime(nextFireTime.toDate());
}

public static int adaptTask(SchedulerTask task, Date time) {
	
	// 调整任务,需要重新计算
	Cron quartzCron = getCron(task.getCron());
	ExecutionTime executionTime = ExecutionTime.forCron(quartzCron);
	DateTime updateCurrentFireTime = new DateTime(time);
	updateCurrentFireTime = executionTime.nextExecution(new DateTime());
	task.setCurrentFiredTime(updateCurrentFireTime.toDate());
	
	DateTime nextFireTime = nextFireTime(updateCurrentFireTime,Long.parseLong(task.getDescribe()));
	task.setNextFiredTime(nextFireTime.toDate());
	
	return isExecutable(task, time);
}

public static DateTime nextFireTime(DateTime currentFireTime, long added) {
	return currentFireTime.withDurationAdded(added, 1);
}

如果正常运行,通过时间间隔获取下一个运行时间点;如果运行异常,重新计算初始运行时间点。

限制条件

  1. 本方法适用于周期运行的时间点
  2. 本方法不适用于日、星期、月、年等存在区间限制,也即crontab表达式,后面几个都是”*”