无论是使用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表达式,后面几个都是”*”

Logo

更多推荐