序
本文主要研究一下PowerJob的任務排程
CoreScheduleTaskManager
tech/powerjob/server/core/scheduler/CoreScheduleTaskManager.java
@Service
@Slf4j
@RequiredArgsConstructor
public class CoreScheduleTaskManager implements InitializingBean, DisposableBean {
private final PowerScheduleService powerScheduleService;
private final InstanceStatusCheckService instanceStatusCheckService;
private final List<Thread> coreThreadContainer = new ArrayList<>();
@SuppressWarnings("AlibabaAvoidManuallyCreateThread")
@Override
public void afterPropertiesSet() {
// 定時排程
coreThreadContainer.add(new Thread(new LoopRunnable("ScheduleCronJob", PowerScheduleService.SCHEDULE_RATE, () -> powerScheduleService.scheduleNormalJob(TimeExpressionType.CRON)), "Thread-ScheduleCronJob"));
coreThreadContainer.add(new Thread(new LoopRunnable("ScheduleDailyTimeIntervalJob", PowerScheduleService.SCHEDULE_RATE, () -> powerScheduleService.scheduleNormalJob(TimeExpressionType.DAILY_TIME_INTERVAL)), "Thread-ScheduleDailyTimeIntervalJob"));
coreThreadContainer.add(new Thread(new LoopRunnable("ScheduleCronWorkflow", PowerScheduleService.SCHEDULE_RATE, powerScheduleService::scheduleCronWorkflow), "Thread-ScheduleCronWorkflow"));
coreThreadContainer.add(new Thread(new LoopRunnable("ScheduleFrequentJob", PowerScheduleService.SCHEDULE_RATE, powerScheduleService::scheduleFrequentJob), "Thread-ScheduleFrequentJob"));
// 資料清理
coreThreadContainer.add(new Thread(new LoopRunnable("CleanWorkerData", PowerScheduleService.SCHEDULE_RATE, powerScheduleService::cleanData), "Thread-CleanWorkerData"));
// 狀態檢查
coreThreadContainer.add(new Thread(new LoopRunnable("CheckRunningInstance", InstanceStatusCheckService.CHECK_INTERVAL, instanceStatusCheckService::checkRunningInstance), "Thread-CheckRunningInstance"));
coreThreadContainer.add(new Thread(new LoopRunnable("CheckWaitingDispatchInstance", InstanceStatusCheckService.CHECK_INTERVAL, instanceStatusCheckService::checkWaitingDispatchInstance), "Thread-CheckWaitingDispatchInstance"));
coreThreadContainer.add(new Thread(new LoopRunnable("CheckWaitingWorkerReceiveInstance", InstanceStatusCheckService.CHECK_INTERVAL, instanceStatusCheckService::checkWaitingWorkerReceiveInstance), "Thread-CheckWaitingWorkerReceiveInstance"));
coreThreadContainer.add(new Thread(new LoopRunnable("CheckWorkflowInstance", InstanceStatusCheckService.CHECK_INTERVAL, instanceStatusCheckService::checkWorkflowInstance), "Thread-CheckWorkflowInstance"));
coreThreadContainer.forEach(Thread::start);
}
//......
}
CoreScheduleTaskManager在afterPropertiesSet的時候會啟動一系列的執行緒,它們都是LoopRunnable型別的,分別排程powerScheduleService.scheduleNormalJob(TimeExpressionType.CRON)、powerScheduleService.scheduleNormalJob(TimeExpressionType.DAILY_TIME_INTERVAL)、powerScheduleService::scheduleCronWorkflow、powerScheduleService::scheduleFrequentJob、powerScheduleService::cleanData、instanceStatusCheckService::checkRunningInstance、instanceStatusCheckService::checkWaitingDispatchInstance、instanceStatusCheckService::checkWaitingWorkerReceiveInstance、instanceStatusCheckService::checkWorkflowInstance
LoopRunnable
@RequiredArgsConstructor
private static class LoopRunnable implements Runnable {
private final String taskName;
private final Long runningInterval;
private final Runnable innerRunnable;
@SuppressWarnings("BusyWait")
@Override
public void run() {
log.info("start task : {}.", taskName);
while (true) {
try {
innerRunnable.run();
Thread.sleep(runningInterval);
} catch (InterruptedException e) {
log.warn("[{}] task has been interrupted!", taskName, e);
break;
} catch (Exception e) {
log.error("[{}] task failed!", taskName, e);
}
}
}
}
LoopRunnable的構造器接收taskName、runningInterval、innerRunnable三個引數,其run方法透過while true迴圈內部執行innerRunnable.run(),執行完sleep指定的runningInterval,若捕獲到InterruptedException則break跳出迴圈,若其他異常則列印error日誌
PowerScheduleService
PowerScheduleService主要提供了scheduleNormalJob、scheduleCronWorkflow、scheduleFrequentJob、cleanData方法
scheduleNormalJob
tech/powerjob/server/core/scheduler/PowerScheduleService.java
public void scheduleNormalJob(TimeExpressionType timeExpressionType) {
long start = System.currentTimeMillis();
// 排程 CRON 表示式 JOB
try {
final List<Long> allAppIds = appInfoRepository.listAppIdByCurrentServer(transportService.defaultProtocol().getAddress());
if (CollectionUtils.isEmpty(allAppIds)) {
log.info("[NormalScheduler] current server has no app's job to schedule.");
return;
}
scheduleNormalJob0(timeExpressionType, allAppIds);
} catch (Exception e) {
log.error("[NormalScheduler] schedule cron job failed.", e);
}
long cost = System.currentTimeMillis() - start;
log.info("[NormalScheduler] {} job schedule use {} ms.", timeExpressionType, cost);
if (cost > SCHEDULE_RATE) {
log.warn("[NormalScheduler] The database query is using too much time({}ms), please check if the database load is too high!", cost);
}
}
scheduleNormalJob方法主要是查詢當前server負責的appId列表,然後內部委託改為scheduleNormalJob0
scheduleNormalJob0
private void scheduleNormalJob0(TimeExpressionType timeExpressionType, List<Long> appIds) {
long nowTime = System.currentTimeMillis();
long timeThreshold = nowTime + 2 * SCHEDULE_RATE;
Lists.partition(appIds, MAX_APP_NUM).forEach(partAppIds -> {
try {
// 查詢條件:任務開啟 + 使用CRON表達排程時間 + 指定appId + 即將需要排程執行
List<JobInfoDO> jobInfos = jobInfoRepository.findByAppIdInAndStatusAndTimeExpressionTypeAndNextTriggerTimeLessThanEqual(partAppIds, SwitchableStatus.ENABLE.getV(), timeExpressionType.getV(), timeThreshold);
if (CollectionUtils.isEmpty(jobInfos)) {
return;
}
// 1. 批次寫日誌表
Map<Long, Long> jobId2InstanceId = Maps.newHashMap();
log.info("[NormalScheduler] These {} jobs will be scheduled: {}.", timeExpressionType.name(), jobInfos);
jobInfos.forEach(jobInfo -> {
Long instanceId = instanceService.create(jobInfo.getId(), jobInfo.getAppId(), jobInfo.getJobParams(), null, null, jobInfo.getNextTriggerTime()).getInstanceId();
jobId2InstanceId.put(jobInfo.getId(), instanceId);
});
instanceInfoRepository.flush();
// 2. 推入時間輪中等待排程執行
jobInfos.forEach(jobInfoDO -> {
Long instanceId = jobId2InstanceId.get(jobInfoDO.getId());
long targetTriggerTime = jobInfoDO.getNextTriggerTime();
long delay = 0;
if (targetTriggerTime < nowTime) {
log.warn("[Job-{}] schedule delay, expect: {}, current: {}", jobInfoDO.getId(), targetTriggerTime, System.currentTimeMillis());
} else {
delay = targetTriggerTime - nowTime;
}
InstanceTimeWheelService.schedule(instanceId, delay, () -> dispatchService.dispatch(jobInfoDO, instanceId, Optional.empty(), Optional.empty()));
});
// 3. 計算下一次排程時間(忽略5S內的重複執行,即CRON模式下最小的連續執行間隔為 SCHEDULE_RATE ms)
jobInfos.forEach(jobInfoDO -> {
try {
refreshJob(timeExpressionType, jobInfoDO);
} catch (Exception e) {
log.error("[Job-{}] refresh job failed.", jobInfoDO.getId(), e);
}
});
jobInfoRepository.flush();
} catch (Exception e) {
log.error("[NormalScheduler] schedule {} job failed.", timeExpressionType.name(), e);
}
});
}
scheduleNormalJob0主要是排程CRON、DAILY_TIME_INTERVAL型別的任務,它透過jobInfoRepository查詢指定appId、狀態啟用、指定TimeExpressionType,以及NextTriggerTime小於等於nowTime + 2 * SCHEDULE_RATE
的任務,然後挨個執行instanceService.create建立任務例項,然後放入到InstanceTimeWheelService.schedule進行排程,最後計算和更新一下每個job的nextTriggerTime
scheduleCronWorkflow
public void scheduleCronWorkflow() {
long start = System.currentTimeMillis();
// 排程 CRON 表示式 WORKFLOW
try {
final List<Long> allAppIds = appInfoRepository.listAppIdByCurrentServer(transportService.defaultProtocol().getAddress());
if (CollectionUtils.isEmpty(allAppIds)) {
log.info("[CronWorkflowSchedule] current server has no app's workflow to schedule.");
return;
}
scheduleWorkflowCore(allAppIds);
} catch (Exception e) {
log.error("[CronWorkflowSchedule] schedule cron workflow failed.", e);
}
long cost = System.currentTimeMillis() - start;
log.info("[CronWorkflowSchedule] cron workflow schedule use {} ms.", cost);
if (cost > SCHEDULE_RATE) {
log.warn("[CronWorkflowSchedule] The database query is using too much time({}ms), please check if the database load is too high!", cost);
}
}
scheduleCronWorkflow主要是排程CRON 表示式 WORKFLOW,內部委託給scheduleWorkflowCore
scheduleFrequentJob
public void scheduleFrequentJob() {
long start = System.currentTimeMillis();
// 排程 FIX_RATE/FIX_DELAY 表示式 JOB
try {
final List<Long> allAppIds = appInfoRepository.listAppIdByCurrentServer(transportService.defaultProtocol().getAddress());
if (CollectionUtils.isEmpty(allAppIds)) {
log.info("[FrequentJobSchedule] current server has no app's job to schedule.");
return;
}
scheduleFrequentJobCore(allAppIds);
} catch (Exception e) {
log.error("[FrequentJobSchedule] schedule frequent job failed.", e);
}
long cost = System.currentTimeMillis() - start;
log.info("[FrequentJobSchedule] frequent job schedule use {} ms.", cost);
if (cost > SCHEDULE_RATE) {
log.warn("[FrequentJobSchedule] The database query is using too much time({}ms), please check if the database load is too high!", cost);
}
}
scheduleFrequentJob主要是排程FIX_RATE/FIX_DELAY 表示式 JOB,內部委託給了scheduleFrequentJobCore
scheduleFrequentJobCore
private void scheduleFrequentJobCore(List<Long> appIds) {
Lists.partition(appIds, MAX_APP_NUM).forEach(partAppIds -> {
try {
// 查詢所有的秒級任務(只包含ID)
List<Long> jobIds = jobInfoRepository.findByAppIdInAndStatusAndTimeExpressionTypeIn(partAppIds, SwitchableStatus.ENABLE.getV(), TimeExpressionType.FREQUENT_TYPES);
if (CollectionUtils.isEmpty(jobIds)) {
return;
}
// 查詢日誌記錄表中是否存在相關的任務
List<Long> runningJobIdList = instanceInfoRepository.findByJobIdInAndStatusIn(jobIds, InstanceStatus.GENERALIZED_RUNNING_STATUS);
Set<Long> runningJobIdSet = Sets.newHashSet(runningJobIdList);
List<Long> notRunningJobIds = Lists.newLinkedList();
jobIds.forEach(jobId -> {
if (!runningJobIdSet.contains(jobId)) {
notRunningJobIds.add(jobId);
}
});
if (CollectionUtils.isEmpty(notRunningJobIds)) {
return;
}
notRunningJobIds.forEach(jobId -> {
Optional<JobInfoDO> jobInfoOpt = jobInfoRepository.findById(jobId);
jobInfoOpt.ifPresent(jobInfoDO -> {
LifeCycle lifeCycle = LifeCycle.parse(jobInfoDO.getLifecycle());
// 生命週期已經結束
if (lifeCycle.getEnd() != null && lifeCycle.getEnd() < System.currentTimeMillis()) {
jobInfoDO.setStatus(SwitchableStatus.DISABLE.getV());
jobInfoDO.setGmtModified(new Date());
jobInfoRepository.saveAndFlush(jobInfoDO);
log.info("[FrequentScheduler] disable frequent job,id:{}.", jobInfoDO.getId());
} else if (lifeCycle.getStart() == null || lifeCycle.getStart() < System.currentTimeMillis() + SCHEDULE_RATE * 2) {
log.info("[FrequentScheduler] schedule frequent job,id:{}.", jobInfoDO.getId());
jobService.runJob(jobInfoDO.getAppId(), jobId, null, Optional.ofNullable(lifeCycle.getStart()).orElse(0L) - System.currentTimeMillis());
}
});
});
} catch (Exception e) {
log.error("[FrequentScheduler] schedule frequent job failed.", e);
}
});
}
scheduleFrequentJobCore主要是排程秒級任務,它先找出秒級任務的id,然後過濾掉正在執行的任務,剩下的未執行的任務挨個判斷是否需要排程,需要則執行jobService.runJob
cleanData
public void cleanData() {
try {
final List<Long> allAppIds = appInfoRepository.listAppIdByCurrentServer(transportService.defaultProtocol().getAddress());
if (allAppIds.isEmpty()) {
return;
}
WorkerClusterManagerService.clean(allAppIds);
} catch (Exception e) {
log.error("[CleanData] clean data failed.", e);
}
}
cleanData主要是透過WorkerClusterManagerService.clean來維護當前server負責的appId快取
InstanceStatusCheckService
InstanceStatusCheckService提供了checkRunningInstance、checkWaitingDispatchInstance、checkWaitingWorkerReceiveInstance、checkWorkflowInstance方法
小結
PowerJob的CoreScheduleTaskManager在afterPropertiesSet的時候會啟動一系列的執行緒,它們都是LoopRunnable型別的,其中scheduleNormalJob主要是排程CRON、DAILY_TIME_INTERVAL型別的任務,scheduleCronWorkflow主要是排程CRON 表示式 WORKFLOW任務,scheduleFrequentJob主要是排程FIX_RATE/FIX_DELAY 表示式 JOB。