故障描述:
线上的job正常跑一段时间后就进入了blocked状态,更新quartz版本仍无法解决
故障分析:
先说正常获取、触发任务执行的流程:
调度器线程执行的时候,首先从 triggers 表获取状态为 WAITING 的将要发射的触发器,然后条件更新状态 TRIGGER_STATE 的值为 ACQUIRED ,更新成功则表示抢占到了,否则可能是被其他调度器抢占。然后插入触发器信息及实例名到 FIRED_TRIGGERS 表。前面的更新与插入操作是在同一个事务里完成。
抢占到触发器后,调度器线程等待触发时间到来。
执行时间到来后,调度器线程首先会把 FIRED_TRIGGERS 表里触发器记录的状态更新为 EXECUTING ,如果任务允许并发执行,把 TRIGGERS 表里的状态更新为 WAITING , PAUSED 或 COMPLETE (不需要再执行的) ;如果任务不允许并发执行,还会把 TRIGGERS 表里的状态更新为 BLOCKED 或 PAUSED_BLOCKED,这个更新是根据 任务名和任务所属组名 而不是触发器名称和触发器所属组名 来更新的,这就解决了一个任务有多个触发器的并发问题;然后调度器线程会创建一个执行环境来执行任务,以便在任务执行完成后更新触发器的状态。任务执行完成后,在一个事务里把 triggers 里的触发器状态更新为 WAITING,删除 FIRED_TRIGGERS 表里对应的记录。
即状态变化为:waiting->blocked->executing->waiting
分析线上job得出blocked原因:
1、job执行线程执行周期过长,下一个执行任务周期到了,导致blocked。一般是业务过多或者http请求未设置超时时长
2、异常处理,导致一直失败重试,可能导致blocked代码如下
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("execute task:{}....", jobExecutionContext.getTrigger().getKey());
try {
runJob(jobExecutionContext);
} catch (Exception e) {
JobExecutionException e2 = new JobExecutionException(e);
e2.setRefireImmediately(true);
throw e2;
}
}
如果runJob方法一直异常,这会一直throw exception,导致blocked。
解决方法:
一、控制job执行不会重叠,保证每个执行周期不会重叠。
1)、如果处理业务很多,可以用多线程的方式,并行处理,缩短执行时长。
2)、配置cron 表达式要注意避免执行周期交叉。
3)、如果使用http或者其他远程调用的方法,一定要设置超时时长
二、异常一定要控制重试次数,代码如下
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("execute task:{}....", jobExecutionContext.getTrigger().getKey());
try {
runJob(jobExecutionContext);
} catch (Exception e) {
JobExecutionException e2 = new JobExecutionException(e);
e2.setRefireImmediately(true);
log.error("execute task:{} exception, setRefireImmediately true", jobExecutionContext.getTrigger().getKey());
Integer result = (Integer) jobExecutionContext.get("result");
if (result == null || result < cronConfig.getRetryCount()) {
jobExecutionContext.put("result", result == null ? 2 : result + 1);
throw e2;
}
}
}