Quartz执行逻辑(六)Misfire的处理

Quartz执行逻辑(六)Misfire的处理

背景

Quartz框架在执行任务中可能会由于停电,宕机等原因,有一段没有去调度任务,但是有些任务可能刚好应该在这个时间内执行,此时就会出现在本该执行的时间没有执行的情况。这种情况就叫做misfire了。

Quartz的misfire机制

为了解决这个问题,Quartz框架也是做出了应对,它提供了多种可选的misfire处理策略,在添加任务时就可以配置,之后在任务misfire时就会根据配置的策略去处理。

Quartz在JobStoreSupport类中声明了一个MisFireHandler线程,用于去扫描QRTZ_TRIGGERS表,检查有没有trigger misfire了。这个线程会在Scheduler start的时候初始化,之后就会一直工作。Run方法中的逻辑如下:

public void run() {
        
        while (!shutdown) {

            long sTime = System.currentTimeMillis();

            RecoverMisfiredJobsResult recoverMisfiredJobsResult = manage();

            if (recoverMisfiredJobsResult.getProcessedMisfiredTriggerCount() > 0) {
                signalSchedulingChangeImmediately(recoverMisfiredJobsResult.getEarliestNewTime());
            }

            if (!shutdown) {
                long timeToSleep = 50l;  // At least a short pause to help balance threads
                if (!recoverMisfiredJobsResult.hasMoreMisfiredTriggers()) {
                    timeToSleep = getMisfireThreshold() - (System.currentTimeMillis() - sTime);
                    if (timeToSleep <= 0) {
                        timeToSleep = 50l;
                    }

                    if(numFails > 0) {
                        timeToSleep = Math.max(getDbRetryInterval(), timeToSleep);
                    }
                }
                
                try {
                    Thread.sleep(timeToSleep);
                } catch (Exception ignore) {
                }
            }//while !shutdown
        }
    }
}

从代码可以看到,首先调用了manage方法,去执行了一次misfire的处理操作,拿到了一个RecoverMisfiredJobsResult的实例,如果有misfire的trigger被处理了,则要立即更新一次调度信号,之后线程至少会sleep50ms,以平衡线程的执行。所以主要需要看一下manage方法。其中调用了doRecoverMisfires,该方法中又调用了RecoverMisfireJobs方法。该方法实现了主要的业务逻辑,第一步:

// If recovering, we want to handle all of the misfired
    // triggers right away.
    int maxMisfiresToHandleAtATime = 
        (recovering) ? -1 : getMaxMisfiresToHandleAtATime();
    
    List<TriggerKey> misfiredTriggers = new LinkedList<TriggerKey>();
    long earliestNewTime = Long.MAX_VALUE;
    // We must still look for the MISFIRED state in case triggers were left 
    // in this state when upgrading to this version that does not support it. 
    boolean hasMoreMisfiredTriggers =
        getDelegate().hasMisfiredTriggersInState(
            conn, STATE_WAITING, getMisfireTime(), 
            maxMisfiresToHandleAtATime, misfiredTriggers);

去QRTZ_TRIGGERS表查最大maxMisfiresToHandleAtTime个misfire的triggers,执行的sql语句如下:

String SELECT_HAS_MISFIRED_TRIGGERS_IN_STATE = "SELECT "
    + COL_TRIGGER_NAME + ", " + COL_TRIGGER_GROUP + " FROM "
    + TABLE_PREFIX_SUBST + TABLE_TRIGGERS + " WHERE "
    + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND NOT ("
    + COL_MISFIRE_INSTRUCTION + " = " + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY + ") AND " 
    + COL_NEXT_FIRE_TIME + " < ? " 
    + "AND " + COL_TRIGGER_STATE + " = ? "
    + "ORDER BY " + COL_NEXT_FIRE_TIME + " ASC, " + COL_PRIORITY + " DESC";

如果有misfire的triggers,则向下进行第二步,处理misfire:

for (TriggerKey triggerKey: misfiredTriggers) {
        
        OperableTrigger trig = 
            retrieveTrigger(conn, triggerKey);

        if (trig == null) {
            continue;
        }

        doUpdateOfMisfiredTrigger(conn, trig, false, STATE_WAITING, recovering);

        if(trig.getNextFireTime() != null && trig.getNextFireTime().getTime() < earliestNewTime)
            earliestNewTime = trig.getNextFireTime().getTime();
    }

    return new RecoverMisfiredJobsResult(
            hasMoreMisfiredTriggers, misfiredTriggers.size(), earliestNewTime);

retrieveTrigger方法是从QRTZ_TRIGGERS表中查询出trigger的数据,doUpdateOfMisfiredTrigger方法中调用了

trig.updateAfterMisfire(cal);

该方法的逻辑就是根据misfire的策略去更新nextFireTime,然后会调用storeTrigger方法去更新QRTZ_TRIGGERS表中该trigger的数据。那Quartz好像到目前为止还是没有执行任务啊,misfire还是没有处理啊。别急,看一下updateAfterMisfire方法,以cron类型的trigger中的该方法为例:

	int instr = getMisfireInstruction();
	
    if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)
        return;

    if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) {
        instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW;
    }

    if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {
        Date newFireTime = getFireTimeAfter(new Date());
        while (newFireTime != null && cal != null
                && !cal.isTimeIncluded(newFireTime.getTime())) {
            newFireTime = getFireTimeAfter(newFireTime);
        }
        setNextFireTime(newFireTime);
    } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {
        setNextFireTime(new Date());
    }

可以看到,如果instr是立即触发一次,那么nextFireTime就会被设置成当前时间,接下来又调用了storeTrigger方法去把trigger的状态置为了WAITING。所以就会被QuartzShcedulerThread线程扫描到,立即执行一次。就实现了misfire的处理,其他的misfire的策略处理方式类似,都是通过更改下一次触发时间决定的。SimpleTriggerImpl中的updateAfterMisfire方法实现有所不同,出去更改nextFireTime还涉及到了次数的处理,有需要的可以自己看下。

有关QuartzSchedulerThread的执行逻辑的可以看链接:Quartz执行逻辑(一)QuartzSchedulerThread介绍.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷福。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值