123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- package com.xxl.job.admin.core.thread;
- import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
- import com.xxl.job.admin.core.cron.CronExpression;
- import com.xxl.job.admin.core.model.XxlJobInfo;
- import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- import java.text.ParseException;
- import java.util.*;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.TimeUnit;
- /**
- * @author xuxueli 2019-05-21
- */
- public class JobScheduleHelper {
- private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
- private static JobScheduleHelper instance = new JobScheduleHelper();
- public static JobScheduleHelper getInstance(){
- return instance;
- }
- public static final long PRE_READ_MS = 5000; // pre read
- private Thread scheduleThread;
- private Thread ringThread;
- private volatile boolean scheduleThreadToStop = false;
- private volatile boolean ringThreadToStop = false;
- private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
- public void start(){
- // schedule thread
- scheduleThread = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
- } catch (InterruptedException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
- // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
- int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
- while (!scheduleThreadToStop) {
- // Scan Job
- long start = System.currentTimeMillis();
- Connection conn = null;
- Boolean connAutoCommit = null;
- PreparedStatement preparedStatement = null;
- boolean preReadSuc = true;
- try {
- conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
- connAutoCommit = conn.getAutoCommit();
- conn.setAutoCommit(false);
- preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
- preparedStatement.execute();
- // tx start
- // 1、pre read
- long nowTime = System.currentTimeMillis();
- List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
- if (scheduleList!=null && scheduleList.size()>0) {
- // 2、push time-ring
- for (XxlJobInfo jobInfo: scheduleList) {
- // time-ring jump
- if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
- // 2.1、trigger-expire > 5s:pass && make next-trigger-time
- logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
- // fresh next
- refreshNextValidTime(jobInfo, new Date());
- } else if (nowTime > jobInfo.getTriggerNextTime()) {
- // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
- // 1、trigger
- JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
- logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
- // 2、fresh next
- refreshNextValidTime(jobInfo, new Date());
- // next-trigger-time in 5s, pre-read again
- if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
- // 1、make ring second
- int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
- // 2、push time ring
- pushTimeRing(ringSecond, jobInfo.getId());
- // 3、fresh next
- refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
- }
- } else {
- // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
- // 1、make ring second
- int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
- // 2、push time ring
- pushTimeRing(ringSecond, jobInfo.getId());
- // 3、fresh next
- refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
- }
- }
- // 3、update trigger info
- for (XxlJobInfo jobInfo: scheduleList) {
- XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
- }
- } else {
- preReadSuc = false;
- }
- // tx stop
- } catch (Exception e) {
- if (!scheduleThreadToStop) {
- logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
- }
- } finally {
- // commit
- if (conn != null) {
- try {
- conn.commit();
- } catch (SQLException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- try {
- conn.setAutoCommit(connAutoCommit);
- } catch (SQLException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- try {
- conn.close();
- } catch (SQLException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- }
- // close PreparedStatement
- if (null != preparedStatement) {
- try {
- preparedStatement.close();
- } catch (SQLException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- }
- }
- long cost = System.currentTimeMillis()-start;
- // Wait seconds, align second
- if (cost < 1000) { // scan-overtime, not wait
- try {
- // pre-read period: success > scan each second; fail > skip this period;
- TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
- } catch (InterruptedException e) {
- if (!scheduleThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- }
- }
- logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
- }
- });
- scheduleThread.setDaemon(true);
- scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
- scheduleThread.start();
- // ring thread
- ringThread = new Thread(new Runnable() {
- @Override
- public void run() {
- // align second
- try {
- TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000 );
- } catch (InterruptedException e) {
- if (!ringThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- while (!ringThreadToStop) {
- try {
- // second data
- List<Integer> ringItemData = new ArrayList<>();
- int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
- for (int i = 0; i < 2; i++) {
- List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
- if (tmpData != null) {
- ringItemData.addAll(tmpData);
- }
- }
- // ring trigger
- logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
- if (ringItemData.size() > 0) {
- // do trigger
- for (int jobId: ringItemData) {
- // do trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
- }
- // clear
- ringItemData.clear();
- }
- } catch (Exception e) {
- if (!ringThreadToStop) {
- logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
- }
- }
- // next second, align second
- try {
- TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000);
- } catch (InterruptedException e) {
- if (!ringThreadToStop) {
- logger.error(e.getMessage(), e);
- }
- }
- }
- logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
- }
- });
- ringThread.setDaemon(true);
- ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
- ringThread.start();
- }
- private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws ParseException {
- Date nextValidTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(fromTime);
- if (nextValidTime != null) {
- jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
- jobInfo.setTriggerNextTime(nextValidTime.getTime());
- } else {
- jobInfo.setTriggerStatus(0);
- jobInfo.setTriggerLastTime(0);
- jobInfo.setTriggerNextTime(0);
- }
- }
- private void pushTimeRing(int ringSecond, int jobId){
- // push async ring
- List<Integer> ringItemData = ringData.get(ringSecond);
- if (ringItemData == null) {
- ringItemData = new ArrayList<Integer>();
- ringData.put(ringSecond, ringItemData);
- }
- ringItemData.add(jobId);
- logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
- }
- public void toStop(){
- // 1、stop schedule
- scheduleThreadToStop = true;
- try {
- TimeUnit.SECONDS.sleep(1); // wait
- } catch (InterruptedException e) {
- logger.error(e.getMessage(), e);
- }
- if (scheduleThread.getState() != Thread.State.TERMINATED){
- // interrupt and wait
- scheduleThread.interrupt();
- try {
- scheduleThread.join();
- } catch (InterruptedException e) {
- logger.error(e.getMessage(), e);
- }
- }
- // if has ring data
- boolean hasRingData = false;
- if (!ringData.isEmpty()) {
- for (int second : ringData.keySet()) {
- List<Integer> tmpData = ringData.get(second);
- if (tmpData!=null && tmpData.size()>0) {
- hasRingData = true;
- break;
- }
- }
- }
- if (hasRingData) {
- try {
- TimeUnit.SECONDS.sleep(8);
- } catch (InterruptedException e) {
- logger.error(e.getMessage(), e);
- }
- }
- // stop ring (wait job-in-memory stop)
- ringThreadToStop = true;
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- logger.error(e.getMessage(), e);
- }
- if (ringThread.getState() != Thread.State.TERMINATED){
- // interrupt and wait
- ringThread.interrupt();
- try {
- ringThread.join();
- } catch (InterruptedException e) {
- logger.error(e.getMessage(), e);
- }
- }
- logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
- }
- }
|