任务调度框架quartz

前言

linux中的cron可以是任务调度的初形。随着任务的复杂性及分布式需求,cron显的力不从心。Quartz是经典的作业调度框架。

Quartz的基础

在深入研究之初,先仿照quartz的原理,设计一个简单的作业调度器。
任务调度框架quartz_第1张图片

  1. 先看job类,这个类,非常简单,只有一个execute方法,该方法是job具体执行的内容:
public class Job {
    
    public void execute(Map<String, String> jobData) {
        System.out.println("定时器任务执行" + new Date(System.currentTimeMillis()));
        System.out.println("参数值"+jobData.get("jobName"));
    }
}
  1. jobdetail类,该类是对具体job类的封装,包括jobName(任务标识),job执行需要的运行时参数jobdata
public class JobDetail {

    private Class<? extends Job> clz;
    private String jobName;
    private Map<String, String> jobData;

    public JobDetail(Class<? extends Job> clz, String jobName) {
        this.clz = clz;
        this.jobName = jobName;
        this.jobData = new HashMap<>();
        this.jobData.put("jobName", this.jobName);
    }

    public String getJobName(){
        return this.jobName;
    }

    public void executeJob(){
        try {
            clz.newInstance().execute(jobData);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. trigger类,记录下次运行作业的时间和运行job的key
public class Trigger implements Comparable<Trigger>{

    private String jobKey;
    private long nextFireTime;


    public Trigger(long nextFireTime) {
        this.nextFireTime = nextFireTime;
    }
    

    // 用于根据触发时间排序
    @Override
    public int compareTo(Trigger o) {
        return (int)( this.nextFireTime - o.nextFireTime);
    }

    public void setJobKey(String jobKey){
        this.jobKey = jobKey;
    }

    public String getJobKey() {
        return jobKey;
    }

    public long getNextFireTime() {
        return nextFireTime;
    }
}
  1. scheduler类,最重要的类,用来启动和停止框架
public class Scheduler {

    private Map<String, JobDetail> jobMap = new HashMap<>();
    private TreeSet<Trigger> triggerList = new TreeSet<>();
    private SchedulerThread schedulerThread = new SchedulerThread();

    public void schedulerJob(JobDetail jobDetail, Trigger trigger){
        jobMap.put(jobDetail.getJobName(),jobDetail);
        trigger.setJobKey(jobDetail.getJobName());
        triggerList.add(trigger);
    }

    public void start(){
        schedulerThread.start();
    }

    public void stop(){
        schedulerThread.halt();
    }
}
  1. scheduler的执行是在scheduler的schedulerThread中执行
public class SchedulerThread extends Thread {

        private boolean shutDown = false;

        @Override
        public void run() {
            while (!shutDown){

                 Trigger trigger = triggerList.pollFirst();

                 if(trigger == null){
                     try {Thread.sleep(100);} catch (InterruptedException e) {}
                     continue;
                 }

                 long cur = System.currentTimeMillis();
                 long next = trigger.getNextFireTime();

                 if(cur <  next){
                     try {Thread.sleep(next - cur);} catch (InterruptedException e) {}
                 }

                JobDetail jobDetail = jobMap.get(trigger.getJobKey());
                jobDetail.executeJob();
            }

        }

        public void halt(){
            shutDown = true;
        }
    }

至此所有的框架代码都已经完成,写个main测试下

public static void main(String[] args) {
        Scheduler scheduler = new Scheduler();
        scheduler.schedulerJob(new JobDetail(Job.class, "job01"), new Trigger(System.currentTimeMillis() + 1000));
        scheduler.schedulerJob(new JobDetail(Job.class, "job02"), new Trigger(System.currentTimeMillis() + 2000));
        scheduler.start();
    }

上述的设计,可以说Qz是去除安全验证和多线程同步问题编写的基本调度任务。下面来看真实案例

public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("定时器任务执行" + new Date(System.currentTimeMillis()));
        JobDataMap map=jobExecutionContext.getMergedJobDataMap();
        System.out.println("参数值"+map.get("uname"));
    }

    public static void main(String[] args) throws Exception {
        //1.创建Scheduler的工厂
        SchedulerFactory sf = new StdSchedulerFactory();

        //2.从工厂中获取调度器实例
        Scheduler scheduler = sf.getScheduler();


        //3.创建JobDetail(作业信息)
        JobDetail jb = JobBuilder.newJob(MyJob.class)
                .withDescription("this is a test job") //job的描述
                .withIdentity("testJob", "testGroup") //job 的name和group
                .build();


        //向任务传递数据
        JobDataMap jobDataMap = jb.getJobDataMap();
        jobDataMap.put("uname", "张三");

        //任务运行的时间,SimpleSchedle类型触发器有效
        long time = System.currentTimeMillis() + 3 * 1000L; //3秒后启动任务
        Date statTime = new Date(time);

        //4.创建Trigger
        //使用SimpleScheduleBuilder或者CronScheduleBuilder
        Trigger t = TriggerBuilder.newTrigger()
                .withDescription("")
                .withIdentity("ramTrigger", "ramTriggerGroup")
                .startAt(statTime)  //默认当前时间启动
                //普通计时器
                //.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(3))//间隔3秒,重复3次
                //表达式计时器
                .withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ?")) //3秒执行一次
                .build();

        //5.注册任务和定时器
        scheduler.scheduleJob(jb, t);

        //6.启动 调度器
        scheduler.start();
    }
}

Quartz的分布式

关于分布式调度的二个核心问题:

  1. Quartz 如何向每台服务器分配资源
  2. Quartz怎么保证在一台挂机的情况下,保证任务不丢失

JobStore

Quartz将调度程序的所有的工作数据包括:Jobs,triggers,日历等信息,交由JobStore管理。JobStore的存储方式分为RAM和JDBC两种。群集模式下使用的JDBC。

下面是分析QuartzSchedulerThread源码解决Quartz 如何向每台服务器分配资源,中JobStore的主要作用函数

任务调度框架quartz_第2张图片

public interface JobStore {

    // 此方法主要是申请将要执行任务:
    //  1. 从triggers表中拉取状态为 WAITING 的 Trigger
    //  2. Trigger状态从WAITING改为 ACQUIRED,
    //  3. 如果2修改成功,则向 FIRED_TRIGGERS 表插入记录
    // 此方法有两种方式保证任务的分配
    //  1. 乐观锁,即任务从WAITING改为 ACQUIRED失败,则表示被别台机子分配,也可能ABA问题引起任务重复执行
    //  2. 悲观锁, 步骤1前,通过Lock表进行上锁
    List<OperableTrigger> acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow);

    // 此方法主要作用在Trigger的触发:
    // 1. 将Trigger状态ACQUIRED, 将 FIRED_TRIGGERS状态改为 EXECUTING
    // 2. 计算trigger的NEXT_FIRE_TIME
    // 3. 如果  NEXT_FIRE_TIME 为空,trigger状态 变成 STATE_COMPLETE, 并不进行4步骤
    // 4. 如果不允许并发执行,将 trigger状态 变成 BLOCKED, 否则 置为wait (这里就是导致上面ABA问题的主要原因)
    List<TriggerFiredResult> triggersFired(List<OperableTrigger> triggers);

    // 通过shellJob结果监听器notifyJobStoreJobComplete进行回调
    //  1. 如果任务正常, 则将FIRED_TRIGGERS记录删除
    void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, Trigger.CompletedExecutionInstruction triggerInstCode);
}

任务调度框架quartz_第3张图片

下面是分析ClusterManager源码解决Quartz 保证任务不丢失

  1. 项目启动时,会这 SCHEDULER_STATE 表 插入数据
  2. 之后维护和 SCHEDULER_STATE 的心跳
  3. 如果发布哪台服务器掉线,则重置任务继续执行 @see JobStoreSupport.clusterRecover

Quartz 架构

经上面分析,我们可以得出Quartz的大致结构图
任务调度框架quartz_第4张图片

主要参考

《quartz源码解析–转》
《记一次Quartz重复调度(任务重复执行)的问题排查》
《Quartz定时任务调度 》

你可能感兴趣的:(#,任务调度,Quartz,任务调度)