任务调度对比 XXL-Job、Quartz

215 阅读2分钟

任务调度是需要准确时刻或固定间隔触发任务,或批量数据异步化处理的场景。
主要有两种实现:

中心化调度 XXLJob

包含调度中心、执行单元。
调度中心负责管理定时任务的具体状态管控、持久化、任务下发。
执行器侧包含任务具体逻辑。收到中心下发的执行请求后执行。
通常通过RPC调度。

非中心化调度 Quartz

每个实例都是执行单元。分布式部署下通过DB读取任务信息并执行。

对比

中心化调度包含调度、执行单元。整体部署会较繁琐。但只有调度中心与DB建连,且一台调度中心可以对接多台执行单元。故对DB压力较小。且部分执行单元宕机,调度中心能够做到容灾、转移的功能。

非中心化调度只有执行单元,每台单元都需要和DB建连管理任务,实例间只能通过DB互相感知。无法对整个集群有效的管控,容灾。

任务调度的并发控制上,XXLJob、Quartz都是通过DB来实现分布式锁。通常来说,定时任务的并发压力较小,使用DB实现即可。

Quartz拿锁 image.png
RAMStore的实现 image.png JDBCStore的实现 image.png XXLJob拿锁 image.png

调用逻辑

Quartz

image.png XXLJob

先拿锁
preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();

![image.png](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4cbd2e0bfb7c44129b6675136295ca83~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NjMxNjc5NzAyMzIzOA==:q75.awebp?rk3s=f64ab15b&x-expires=1771949176&x-signature=WBlofx1vyDlOUoIRaMGNbqYEvvc%3D)
// 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) {
            
            超过触发时间5s,根据漏发策略处理。
            // 2.1、trigger-expire > 5s:pass && make next-trigger-time
            logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());

            // 1、misfire match
            MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
            if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
                // FIRE_ONCE_NOW 》 trigger
                JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
                logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
            }

            // 2、fresh next
            refreshNextValidTime(jobInfo, new Date());

        } else if (nowTime > jobInfo.getTriggerNextTime()) {
            超过触发时间5s内,立即触发。
            // 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()) {
                5s内到达触发时间
                // 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);
    }

根据超时情况,选择快/慢线程池提交 。 image.png