Quartz框架设计以及原理

​ 前面8节都是这个框架的几个组件 , 第9节讲的是全部执行流程,最后一节就是总结了 . 希望有兴趣的可以看看 , 提升一下自己 , 不难

1. SchedulerFactory

有俩实现类, 前面一节我们使用的是StanderScheduleFactory , 其实对于快速使用的话, DirectScheduleFactory 比较好用 .

启动 :

// 直接默认启动
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// 或者手动注入配置
Properties properties = new Properties();
properties.setProperty("org.quartz.scheduler.instanceName", "MyScheduler");
properties.setProperty("org.quartz.threadPool.threadCount", "4");
properties.setProperty("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
StdSchedulerFactory factory = new StdSchedulerFactory(properties);
Scheduler scheduler = factory.getScheduler();

// 3. 系统传参,指定配置文件位置 : org.quartz.properties=quartz.properties

//4. 直接不用传参 ,在Classpath下面建立一个 quartz.properties文件就行了. 
复制代码

做了什么 ? 配置咋加载进去的, 其实就在这里 org.quartz.impl.StdSchedulerFactory#getDefaultScheduler

他里面实例化了一堆 properties , 具体实现,我真的觉得么写, 代码太简单 了 . 他会有一个默认的实现, 所以不怕因为配置不写, 就启动错误. 或者你直接构造方法传入配置也行 .

public static Scheduler getDefaultScheduler() throws SchedulerException {
    StdSchedulerFactory fact = new StdSchedulerFactory();
    return fact.getScheduler();
}
复制代码

对于 org.quartz.impl.StdSchedulerFactory#instantiate() 方法比较核心 获取 Scheduler核心方法 代码有上千行,不展示了 , 主要目的就是 初始化一堆的东西 , 封装一堆的东西

主要就是下面这几个

JobStore js = null;
ThreadPool tp = null;
QuartzScheduler qs = null;
DBConnectionManager dbMgr = null;
复制代码

这四个都有共同的特点,

  • 第一 properties注入全部采用的是 set方法反射注入 , org.quartz.impl.StdSchedulerFactory#setBeanProps 这个方法里, 会set注入进去.
  • 第二 全部初始化都是 initialize方法

2. JobStore

​ The interface to be implemented by classes that want to provide a Job and Trigger storage mechanism for the org.quartz.core.QuartzScheduler's use.

Storage of Job s and Trigger s should be keyed on the combination of their name and group for uniqueness.

解释很清楚 : 提供 a Job and Trigger 存储的给QuartzScheduler's 使用. 同时Job s and Trigger的key应该是独一无二的.

org.quartz.spi.JobStore 很显然就是存储我们Job的东西, 我们如果自己不去告诉他用哪个, 默认实现的是 org.quartz.simpl.RAMJobStore

一个基于内存的存储, 很简单一堆集合包住就行了 . 主要注意的是 他会将Job 包装一下称为 JobWrapper

主要是支持 三种存储配置 :

  • RAM (直接JVM进程的)
  • terracotta (Terracotta是一款由美国Terracotta公司开发的著名开源Java集群平台。)
  • JDBC (MySQL 之类的 ....)

3. ThreadPool

The interface to be implemented by classes that want to provide a thread pool for the org.quartz.core.QuartzScheduler's use.

ThreadPool implementation instances should ideally be made for the sole use of Quartz. Most importantly, when the method blockForAvailableThreads() returns a value of 1 or greater, there must still be at least one available thread in the pool when the method runInThread(Runnable) is called a few moments (or many moments) later. If this assumption does not hold true, it may result in extra JobStore queries and updates, and if clustering features are being used, it may result in greater imballance of load.

4. QuartzSchedulerResources

这个就是整个Quartz 的核心资源, 所有资源都在这里.

5. QuartzScheduler

整个框架的心脏 ....

​ This is the heart of Quartz, an indirect implementation of the Scheduler interface, containing methods to schedule Jobs, register JobListener instances, etc.

​ 是 quartz的核心, 包含调用job的方法, 注册listener

构造器 , 所以把核心资源 QuartzSchedulerResources 也给QuartzScheduler

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval){....}
复制代码

QuartzSchedulerResources

​ Contains all of the resources (JobStore,ThreadPool, etc.) necessary to create a QuartzScheduler instance.

​ resources 资源管理器, 包含job和线程池

idleWaitTime

​ When the scheduler finds there is no current trigger to fire, how long it should wait until checking again...

​ 就是等待实现, 这个是个死循环, 如果等待时间过短, 会空转很快, 所以就是这个意思, 默认是 30S

dbRetryInterval

deprecated 废弃了, 所以就是交给别人管理了 ,

主要实现 : 就他自己....

6. DBConnectionManager

​ Manages a collection of ConnectionProviders, and provides transparent access to their connections.

管理 ConnectionProviders , 提供透明的连接访问 .

其实他就是

JobStore -> DBConnectionManager -> ConnectionProviders , 这个可以说是桥接模式. 有一个管理者管理连接的提供者, 然后JobStore 可以通过他获取连接.

7. JobListener 和 TriggerListener

首先一点就是 这些都是 ListenerManager 进行管理的 , 他的实现类时 ListenerManagerImpl

我们看看 JobListener

​ The interface to be implemented by classes that want to be informed when a JobDetail executes. In general, applications that use a Scheduler will not have use for this mechanism.

​ a JobDetail executes.会通知你 , 通常使用Scheduler的应用是不会使用这个listener的

public interface JobListener {
    String getName();

    // Job调用前
    void jobToBeExecuted(JobExecutionContext context);

    // veto 否决的意思 , 由 org.quartz.TriggerListener#vetoJobExecution 决定
    void jobExecutionVetoed(JobExecutionContext context);

    // Job调用后
    void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);
}
复制代码
public interface TriggerListener {

    String getName();

    // 触发Trigger
    void triggerFired(Trigger trigger, JobExecutionContext context);

    // 禁止执行JobExecution
    boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    // triggerMisfired
    void triggerMisfired(Trigger trigger);

    void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode);

}
复制代码

执行流程就是下面的 .

triggerFired ->

​ vetoJobExecution ? false -> jobToBeExecuted - > 调用Job接口中的方法 - > jobWasExecuted

​ ? true -> jobExecutionVetoed

使用很简单 实现俩接口就行了,

org.quartz.jobListener.j1.class=com.example.springquartz.MyJobListener
// 比如属性设置可以这么做, 只要实现set方法就可以
// org.quartz.jobListener.l1.name=MyJobListener

org.quartz.triggerListener.t1.class=com.example.springquartz.MyTriggerListener
复制代码

8. Scheduler

​ This is the main interface of a Quartz Scheduler.

这个是 一个Quartz Scheduler的主接口 , 对外暴露的唯一接口, 可以操作所有资源对象. 主要原因还是他里面包含了 QuartzScheduler , 所以可以操作其他对象.

我们一般是 StdSchedule

主要实现 :

9. 核心运作流程 .

QuartzSchedulerResources 把所有东西都放进去了

org.quartz.core.QuartzScheduler#QuartzScheduler 是执行的任务的核心 , 就是心脏 . 然后交给了 -> QuartzSchedulerThread 轮询处理

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated long dbRetryInterval)
    throws SchedulerException {
    this.resources = resources;
    if (resources.getJobStore() instanceof JobListener) {
        addInternalJobListener((JobListener)resources.getJobStore());
    }

    // 核心处理线程 - > 这个就是整个心脏 
    this.schedThread = new QuartzSchedulerThread(this, resources);
    ThreadExecutor schedThreadExecutor = resources.getThreadExecutor();
    // 心脏启动 
    schedThreadExecutor.execute(this.schedThread);
    if (idleWaitTime > 0) {
        this.schedThread.setIdleWaitTime(idleWaitTime);
    }

    jobMgr = new ExecutingJobsManager();
    addInternalJobListener(jobMgr);
    errLogger = new ErrorLogger();
    addInternalSchedulerListener(errLogger);

    signaler = new SchedulerSignalerImpl(this, this.schedThread);

    getLog().info("Quartz Scheduler v." + getVersion() + " created.");
}
复制代码

org.quartz.core.QuartzSchedulerThread#run -> 这里描述是 The main processing loop of the QuartzSchedulerThread.

我也不清楚是做啥的 , 但是主要是用来处理Job的 , 封装Job

@Override
public void run() {
    int acquiresFailed = 0;

    while (!halted.get()) {
        try {
            // 因为提前开启了 , 所以需要等待真正启动了- > 调用start, 才能继续执行. paused暂停.在
            // org.quartz.core.QuartzScheduler#start执行. 
            // check if we're supposed to pause...
            synchronized (sigLock) {
                while (paused && !halted.get()) {
                    try {
                        // wait until togglePause(false) is called...
                        sigLock.wait(1000L);
                    } catch (InterruptedException ignore) {
                    }

                    // reset failure counter when paused, so that we don't
                    // wait again after unpausing
                    acquiresFailed = 0;
                }

                if (halted.get()) {
                    break;
                }
            }

            // wait a bit, if reading from job store is consistently
            // failing (e.g. DB is down or restarting)..
            if (acquiresFailed > 1) {
                try {
                    long delay = computeDelayForRepeatedErrors(qsRsrcs.getJobStore(), acquiresFailed);
                    Thread.sleep(delay);
                } catch (Exception ignore) {
                }
            }

            // 这里是获取正在等待的ThreadPool中的线程 . 如果有执行,你要知道`QuartzSchedulerResources` 把所有东西都放进去了
            int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
            if(availThreadCount > 0) { // will always be true, due to semantics of blockForAvailableThreads...

                // 获取当前时刻的triggers
                List<OperableTrigger> triggers;

                long now = System.currentTimeMillis();

                clearSignaledSchedulingChange();
                try {
                    triggers = qsRsrcs.getJobStore().acquireNextTriggers(
                            now + idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
                    acquiresFailed = 0;
                    if (log.isDebugEnabled())
                        log.debug("batch acquisition of " + (triggers == null ? 0 : triggers.size()) + " triggers");
                } catch (JobPersistenceException jpe) {
                    if (acquiresFailed == 0) {
                        // 打印日志
                        qs.notifySchedulerListenersError(
                            "An error occurred while scanning for the next triggers to fire.",
                            jpe);
                    }
                    if (acquiresFailed < Integer.MAX_VALUE)
                        acquiresFailed++;
                    continue;
                } catch (RuntimeException e) {
                    if (acquiresFailed == 0) {
                        getLog().error("quartzSchedulerThreadLoop: RuntimeException "
                                +e.getMessage(), e);
                    }
                    if (acquiresFailed < Integer.MAX_VALUE)
                        acquiresFailed++;
                    continue;
                }

                // 这里就是如果有triggers继续执行
                if (triggers != null && !triggers.isEmpty()) {
                    now = System.currentTimeMillis();
                    long triggerTime = triggers.get(0).getNextFireTime().getTime();
                    long timeUntilTrigger = triggerTime - now;
                    // 这个处理比较灵性.. 死循环, 你差多久, 我就 wait多久
                    while(timeUntilTrigger > 2) {
                        synchronized (sigLock) {
                            if (halted.get()) {
                                break;
                            }
                            if (!isCandidateNewTimeEarlierWithinReason(triggerTime, false)) {
                                try {
                                    // wait
                                    // we could have blocked a long while
                                    // on 'synchronize', so we must recompute
                                    now = System.currentTimeMillis();
                                    timeUntilTrigger = triggerTime - now;
                                    if(timeUntilTrigger >= 1)
                                        sigLock.wait(timeUntilTrigger);
                                } catch (InterruptedException ignore) {
                                }
                            }
                        }
                        if(releaseIfScheduleChangedSignificantly(triggers, triggerTime)) {
                            break;
                        }
                        now = System.currentTimeMillis();
                        timeUntilTrigger = triggerTime - now;
                    }

                    // this happens if releaseIfScheduleChangedSignificantly decided to release triggers
                    // 防止其他发生
                    if(triggers.isEmpty())
                        continue;

                    // 初始化一个 TriggerFiredResult
                    // set triggers to 'executing'
                    List<TriggerFiredResult> bndles = new ArrayList<TriggerFiredResult>();

                    boolean goAhead = true;
                    synchronized(sigLock) {
                        goAhead = !halted.get();
                    }
                    if(goAhead) {
                        try {
                            List<TriggerFiredResult> res = qsRsrcs.getJobStore().triggersFired(triggers);
                            if(res != null)
                                bndles = res;
                        } catch (SchedulerException se) {
                            qs.notifySchedulerListenersError(
                                    "An error occurred while firing triggers '"
                                            + triggers + "'", se);
                            //QTZ-179 : a problem occurred interacting with the triggers from the db
                            //we release them and loop again
                            for (int i = 0; i < triggers.size(); i++) {
                                qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            }
                            continue;
                        }

                    }

                    // 循环执行
                    for (int i = 0; i < bndles.size(); i++) {
                        // 
                        TriggerFiredResult result =  bndles.get(i);
                        TriggerFiredBundle bndle =  result.getTriggerFiredBundle();
                        Exception exception = result.getException();

                        if (exception instanceof RuntimeException) {
                            getLog().error("RuntimeException while firing trigger " + triggers.get(i), exception);
                            qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            continue;
                        }

                        // it's possible to get 'null' if the triggers was paused,
                        // blocked, or other similar occurrences that prevent it being
                        // fired at this time...  or if the scheduler was shutdown (halted)
                        if (bndle == null) {
                            qsRsrcs.getJobStore().releaseAcquiredTrigger(triggers.get(i));
                            continue;
                        }

                        // 初始化任务 
                        JobRunShell shell = null;
                        try {
                            // 初始化流程
                            shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
                            shell.initialize(qs);
                        } catch (SchedulerException se) {
                            qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                            continue;
                        }

                        // 这里就是任务执行了. ... 这里就涉及到 -> `org.quartz.simpl.SimpleThreadPool.WorkerThread#run()` 这里了
                        if (qsRsrcs.getThreadPool().runInThread(shell) == false) {
                            // this case should never happen, as it is indicative of the
                            // scheduler being shutdown or a bug in the thread pool or
                            // a thread pool being used concurrently - which the docs
                            // say not to do...
                            getLog().error("ThreadPool.runInThread() return false!");
                            qsRsrcs.getJobStore().triggeredJobComplete(triggers.get(i), bndle.getJobDetail(), CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR);
                        }

                    }

                    continue; // while (!halted)
                }
            } else { // if(availThreadCount > 0)
                // should never happen, if threadPool.blockForAvailableThreads() follows contract
                continue; // while (!halted)
            }

            long now = System.currentTimeMillis();
            long waitTime = now + getRandomizedIdleWaitTime();
            long timeUntilContinue = waitTime - now;
            synchronized(sigLock) {
                try {
                  if(!halted.get()) {
                    // QTZ-336 A job might have been completed in the mean time and we might have
                    // missed the scheduled changed signal by not waiting for the notify() yet
                    // Check that before waiting for too long in case this very job needs to be
                    // scheduled very soon
                    if (!isScheduleChanged()) {
                      sigLock.wait(timeUntilContinue);
                    }
                  }
                } catch (InterruptedException ignore) {
                }
            }

        } catch(RuntimeException re) {
            getLog().error("Runtime error occurred in main trigger firing loop.", re);
        }
    } // while (!halted)

    // drop references to scheduler stuff to aid garbage collection...
    qs = null;
    qsRsrcs = null;
}
复制代码

这个是启动器, 我们知道启动必须执行一个 Thread.start , 所以不出意外, 上面这个线程他绝对是守护线程, 所有都是, 因为我们start后不阻塞, 直接GG , JVM退出.

org.quartz.core.QuartzScheduler#start -> 这里开始. 核心是 schedThread.togglePause(false) ,

public void start() throws SchedulerException {
    if (shuttingDown|| closed) {
        throw new SchedulerException(
                "The Scheduler cannot be restarted after shutdown() has been called.");
    }

    // QTZ-212 : calling new schedulerStarting() method on the listeners
    // right after entering start()
    notifySchedulerListenersStarting();

    if (initialStart == null) {
        initialStart = new Date();
        this.resources.getJobStore().schedulerStarted();            
        startPlugins();
    } else {
        resources.getJobStore().schedulerResumed();
    }

    // 这里设置的目的就是将它继续执行 -> `org.quartz.core.QuartzSchedulerThread#run`252取消暂停.
    schedThread.togglePause(false);

    getLog().info(
            "Scheduler " + resources.getUniqueIdentifier() + " started.");

    notifySchedulerListenersStarted();
}
复制代码

调用 qsRsrcs.getThreadPool().runInThread(shell) -> 调用 org.quartz.simpl.SimpleThreadPool#runInThread -> 这个然后下面

public boolean runInThread(Runnable runnable) {
    if (runnable == null) {
        return false;
    }

    synchronized (nextRunnableLock) {

        handoffPending = true;

        // Wait until a worker thread is available , 这里就是等 , 等有可用的线程,这里我不理解为啥不使用队列... 其实可以使用队列的. 
        while ((availWorkers.size() < 1) && !isShutdown) {
            try {
                nextRunnableLock.wait(500);
            } catch (InterruptedException ignore) {
            }
        }

        if (!isShutdown) {
            // 等到了, 拿着第一个可用线程执行 
            WorkerThread wt = (WorkerThread)availWorkers.removeFirst();
            busyWorkers.add(wt);
            wt.run(runnable);
        } else {
            // If the thread pool is going down, execute the Runnable
            // within a new additional worker thread (no thread from the pool).
            WorkerThread wt = new WorkerThread(this, threadGroup,
                    "WorkerThread-LastJob", prio, isMakeThreadsDaemons(), runnable);
            busyWorkers.add(wt);
            workers.add(wt);
            wt.start();
        }
        // 释放锁
        nextRunnableLock.notifyAll();
        handoffPending = false;
    }

    // 返回OK , 这里其实是异步的 , 因为处理流程交给了子线程, 他只是返回了他已经处理了, 但是有一个问题就是线程处理时间过长, 就会影响周期性.(所以建议设置多点,但是也不好,线程一直处于不断运行阶段)
    return true;
}

复制代码

调用 wt.run(runnable); -> 调用org.quartz.simpl.SimpleThreadPool.WorkerThread#run(java.lang.Runnable) 执行 run方法

public void run(Runnable newRunnable) {
    synchronized(lock) {
        if(runnable != null) {
            throw new IllegalStateException("Already running a Runnable!");
        }

        runnable = newRunnable;
        lock.notifyAll();
    }
}
复制代码

然后执行等待着的线程中 , 死死的等哇 -> 等下面这个执行 ..

org.quartz.simpl.SimpleThreadPool.WorkerThread#run() 每一个工作线程初始化的时候都是在这里一直的等待哇. 等哇等 , 等哇等.

@Override
public void run() {
    boolean ran = false;

    // 转 ....
    while (run.get()) {
        try {
            // 同步等待. 500ms , 直到有 runnable ... 很可怜, 其实基本大多都是这么实现的 eventloop
            synchronized(lock) {
                while (runnable == null && run.get()) {
                    lock.wait(500);
                }

                if (runnable != null) {
                    ran = true;
                    // 启动任务, 这里直接将Runnable对象启动, 而不是new thread(runnable).start,而是直接调用的方式. 让这个线程去处理. 
                    runnable.run();
                }
            }
        } catch (InterruptedException unblock) {
            // do nothing (loop will terminate if shutdown() was called
            try {
                getLog().error("Worker thread was interrupt()'ed.", unblock);
            } catch(Exception e) {
                // ignore to help with a tomcat glitch
            }
        } catch (Throwable exceptionInRunnable) {
            try {
                getLog().error("Error while executing the Runnable: ",
                    exceptionInRunnable);
            } catch(Exception e) {
                // ignore to help with a tomcat glitch
            }
        } finally {
            synchronized(lock) {
                runnable = null;
            }
            // repair the thread in case the runnable mucked it up...
            if(getPriority() != tp.getThreadPriority()) {
                setPriority(tp.getThreadPriority());
            }

            if (runOnce) {
                   run.set(false);
                clearFromBusyWorkersList(this);
            } else if(ran) {
                ran = false;
                makeAvailable(this);
            }
        }
    }

    //if (log.isDebugEnabled())
    try {
        getLog().debug("WorkerThread is shut down.");
    } catch(Exception e) {
        // ignore to help with a tomcat glitch
    }
}
复制代码

wait 使用

public class Demo {
    public static final Object OBJECT = new Object();

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        while (true) {
            synchronized (OBJECT) {
                System.out.println("time : " + (System.currentTimeMillis() - start) + "ms.");
                OBJECT.wait(1000L);
            }
        }
    }
}
复制代码

输出

time : 0ms.
time : 1000ms.
time : 2001ms.
time : 3002ms.
time : 4002ms.
time : 5003ms.
复制代码

10. 总结

Quartz框架分析了一波, 主要就是类似于NIO的思想 , 主线程不断的监听和轮询worker线程, 同时还要监听Trigger , 看看能不能触发了, 可以了,找个闲着的work线程去处理 , 所以就是NIO的思想.

但是有几个问题, 如果初始化线程(work线程)过多, 那么这些线程在初始化阶段就已经启动了 , 所以很消耗CPU资源,

如果初始化线程(work线程)过少 , 那么就容易发生阻塞的现象, 所以 ..................

他的设计架构还是很不错的 , 可以说是设计模式, 整体的设计完全解耦 . 相当nice. 其实学习框架就是提高自己的设计思想, 不断的学习不断的发现新大陆 .

猜你喜欢

转载自juejin.im/post/5e314b43f265da3e1135a84d