XXL-job如何从数据库拿取数据

本文介绍JobScheduleHelper与JobTriggerPoolHelper在XXL-Job中的协作,涉及数据库操作、定时任务调度、线程池设计及任务执行。通过行锁确保并发安全,讲解了start(), refreshNextValidTime(), pushTimeRing()方法的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前提:

首先我们要了解JobScheduleHelper和JobTriggerPoolHelper这两个类

JobScheduleHelper:从数据库按照一定的规则去拿定时任务的数据

JobTriggerPoolHelper:执行具体任务

JobScheduleHelper

这个类是从数据库按照一定规则去拿定时任务的数据,如下图

下面我们看代码,里面有四个方法,我们只关注三个start(),refreshNextValidTime(),pushTimeRing()

 start():开启一个线程从数据库按照一定规则拿到数据

refreshNextValidTime():更新数据库(三个字段:上一个,下一个触发时间和状态,可根据自己的业务进行拓展)

pushTimeRing():将执行时间超前5s内放到map中,由ringThread线程去执行

start()

三个重点部分:

  • 用数据库行锁来实现调度中心多节点部署,保证触发器的名称和执行时间相同,则只且仅有一个调度中心节点去下发任务给执行器。
  • 里面两个for循环,根据规则不断拿数据,不断更新数据
  • 两个线程scheduleThread(执行时间过期超过5秒和小于5秒)和ringThread(执行时间超前5秒),这里涉及到ringThread如何精准执行
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<QuartzJob>> ringData = new ConcurrentHashMap<>();


    public void start() {

        scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis() % 1000);
                } catch (InterruptedException e) {
                    if (!scheduleThreadToStop) {
                        Logit.errorLog(e.getMessage(), e);
                    }
                }
                Logit.debugLog(">>>>>>>>> init xxl-job admin scheduler success.");

                // 快慢线程池的最大线程数(getTriggerPoolFastMax= 200,getTriggerPoolSlowMax=100),最终等于6000
                // 快慢线程池的最大线程数*触发qps(每次触发消耗50毫秒,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 quartz_lock where lock_name = 'schedule_lock' for update");
                        preparedStatement.execute();

                        // 1、pre read
                        long nowTime = System.currentTimeMillis();
                        List<QuartzJob> quartzJobList = XxlJobAdminConfig.getAdminConfig().getQuartzJobMapper().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
                        if (quartzJobList != null && quartzJobList.size() > 0) {
                            // 2、push time-ring
                            for (QuartzJob quartzJob : quartzJobList) {

                                // 执行时间过期超过5秒,此次不执行,计算出下次触发时间并持久化
                                if (nowTime > quartzJob.getTriggerNextTime() + PRE_READ_MS) {
                                    // 2.1、trigger-expire > 5s:pass && make next-trigger-time
                                    Logit.warnLog(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + quartzJob.getJobId());

                                    // 刷新数据库中下次和上次执行时间
                                    refreshNextValidTime(quartzJob, new Date());

                                    // 执行时间过期小于5秒,立即触发一次,并计算下次执行时间
                                } else if (nowTime > quartzJob.getTriggerNextTime()) {

                                    // 触发一次,此处可以设置为自己自定义的执行方法
                                   JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null);

                                    Logit.debugLog(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + quartzJob.getJobId());

                                    // 刷新数据库中下次和上次执行时间
                                    refreshNextValidTime(quartzJob, new Date());

                                    // 下次执行时间比当前时间在5s内,预读到缓存ringData中
                                    if (quartzJob.getDeleteStatus() == 0 && nowTime + PRE_READ_MS > quartzJob.getTriggerNextTime()) {

                                        // 1、make ring second
                                        int ringSecond = (int) ((quartzJob.getTriggerNextTime() / 1000) % 60);

                                        // 2、push time ring
                                        pushTimeRing(ringSecond, quartzJob);

                                        // 3、fresh next
                                        refreshNextValidTime(quartzJob, new Date(quartzJob.getTriggerNextTime()));
                                    }

                                } else {
                                    // 执行时间超前5秒,未过期,加入到预读缓存中
                                    int ringSecond = (int) ((quartzJob.getTriggerNextTime() / 1000) % 60);

                                    // 2、push time ring
                                    pushTimeRing(ringSecond, quartzJob);

                                    // 刷新数据库中下次和上次执行时间
                                    refreshNextValidTime(quartzJob, new Date(quartzJob.getTriggerNextTime()));
                                }
                            }

                            // 3、更新数据库
                            for (QuartzJob quartzJob : quartzJobList) {
                                XxlJobAdminConfig.getAdminConfig().getQuartzJobMapper().scheduleUpdate(quartzJob);
                            }

                        } else {
                            preReadSuc = false;
                        }

                        // tx stop


                    } catch (Exception e) {
                        if (!scheduleThreadToStop) {
                            Logit.errorLog(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
                        }
                    } finally {

                        // commit
                        if (conn != null) {
                            try {
                                conn.commit();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    Logit.errorLog(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.setAutoCommit(connAutoCommit);
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    Logit.errorLog(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    Logit.errorLog(e.getMessage(), e);
                                }
                            }
                        }

                        // close PreparedStatement
                        if (null != preparedStatement) {
                            try {
                                preparedStatement.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    Logit.errorLog(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) {
                                Logit.errorLog(e.getMessage(), e);
                            }
                        }
                    }

                }
                Logit.debugLog(">>>>>>>>>>> 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) {
                        Logit.errorLog(e.getMessage(), e);
                    }
                }

                while (!ringThreadToStop) {

                    try {
                        // second data
                        List<QuartzJob> ringItemData = new ArrayList<>();
                        int nowSecond = Calendar.getInstance().get(Calendar.SECOND);   // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
                        for (int i = 0; i < 2; i++) {
                            List<QuartzJob> tmpData = ringData.remove((nowSecond + 60 - i) % 60);
                            if (tmpData != null) {
                                ringItemData.addAll(tmpData);
                            }
                        }

                        // ring trigger
                        Logit.debugLog(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData));
                        if (ringItemData.size() > 0) {
                            // do trigger
                            for (QuartzJob quartzJob : ringItemData) {
                                // do trigger
                                excute(quartzJob);
                            }
                            // clear
                            ringItemData.clear();
                        }
                    } catch (Exception e) {
                        if (!ringThreadToStop) {
                            Logit.errorLog(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                        }
                    }

                    // next second, align second
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
                    } catch (InterruptedException e) {
                        if (!ringThreadToStop) {
                            Logit.errorLog(e.getMessage(), e);
                        }
                    }
                }
                Logit.debugLog(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
            }
        });
        ringThread.setDaemon(true);
        ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
        ringThread.start();
    }
}

refreshNextValidTime

    private void refreshNextValidTime(QuartzJob quartzJob, Date fromTime) throws ParseException {
        // 计算下一次触发时间
        Date nextValidTime = new CronExpression(quartzJob.getJobRule()).getNextValidTimeAfter(fromTime);
        if (nextValidTime != null) {
            quartzJob.setTriggerLastTime(quartzJob.getTriggerNextTime());
            quartzJob.setTriggerNextTime(nextValidTime.getTime());
        } else {
            quartzJob.setJobStatus(1);
            quartzJob.setTriggerLastTime(0);
            quartzJob.setTriggerNextTime(0);
        }
    }

pushTimeRing

    private void pushTimeRing(int ringSecond, QuartzJob quartzJob) {
        // 向map中推数据
        List<QuartzJob> ringItemData = ringData.get(ringSecond);
        if (ringItemData == null) {
            ringItemData = new ArrayList<QuartzJob>();
            ringData.put(ringSecond, ringItemData);
        }
        ringItemData.add(quartzJob);

        Logit.debugLog(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData));
    }

JobTriggerPoolHelper

重点:

  • 两个线程池:快,慢线程池
  • 线程池执行我们具体的定时任务的业务

快慢线程池

  public void start(){
        fastTriggerPool = new ThreadPoolExecutor(
                10,  // 核心线程数
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),  //最大线程数 200
                60L,  // 空闲时间
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(1000),  // 先进先出的阻塞队列,存放任务,这个队列只能用于被 execute 调用的 Runnable 任务。
                new ThreadFactory() {  // 创建新线程使用的工厂。
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
                    }
                });

        slowTriggerPool = new ThreadPoolExecutor(
                10,
                XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(2000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
                    }
                });
    }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值