Quartz-Job应用

最近在写Job应用,打算整理一下。

可以在http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。

先解释一下什么叫调度:(来自维基百科)调度,在计算机中是分配工作所需资源的方法,这里的资源可以指虚拟的计算资源,如线程、进程或数据流;也可以指硬件资源,如处理器、网络连接或扩展卡。进行调度工作的程序叫调度器。

分布式与集群:(引用)小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。 

可参考这篇文章:https://blog.csdn.net/u013142781/article/details/51307229

Quartz基本概念:

它是一个优秀的开源调度框架,有强大的调度功能,应用方式灵活(支持任务和调度的各种组合方式,支持调度数据的多种储存方式),有分布式和集群能力。而且,Quartz是Spring默认的调度框架,很容易与Spring集成实现灵活可配置的调度功能。

Quartz调度核心元素:

  1. Scheduler:任务调度器,实际执行任务调度的控制器,在Spring中通过SchedulerFactoryBean封装起来。
  2. Trigger:触发器,用于定义任务调度的时间规则,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比较多。
  3. Calendar:它是一些日历特定时间点的集合。一个trigger可以包含多个Calendar,以便排除或包含某些时间点。
  4. JobDetail:描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。
  5. Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的,在quartz中是给实现的Job添加@DisallowConcurrentExecution注解(以前是实现StatefulJob接口,现在已被Deprecated),在与spring结合中可以在spring配置文件的job detail中配置concurrent参数。

 Quartz集群配置:

主要通过数据库表来感知其他的应用的,各个节点之间并没有直接的通信。只有使用持久的JobStore才能完成Quartz集群。具体的表不加描述。

Quartz基本原理:

主要就是三个核心元素:Scheduler、trigger、job,Scheduler是实际执行调度的控制器,trigger和Job是任务调度的元数据。

Scheduler:主要有三种RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。主要由Scheduler工厂创建DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多。

trigger:用于定义调度时间,按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。

Job:表示被调度的任务。主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。

元素之间关系图

å¾ 1. Quartz æ ¸å¿åç´ å³ç³»å¾

工作原理:

  1. IOC容器初始化时(用Spring与Quartz结合的)会创建并初始化Quartz线程池(TreadPool),并启动它。刚启动时线程池中每个线程都处于等待状态,等待外界给他分配Runnable(持有作业对象的线程)。
  2. 初始化并启动Quartz的主线程(QuartzSchedulerThread),该线程自启动后就会等待外界的信号量开始工作。外界给出工作信号量之后,该主线程的run方法才实质上开始工作。run中会获取JobStore中下一次要触发的作业,拿到之后会一直等待到该作业的真正触发时间,然后将该作业包装成一个JobRunShell对象(该对象实现了Runnable接口,其实看是上面TreadPool中等待外界分配给他的Runnable),然后将刚创建的JobRunShell交给线程池,由线程池负责执行作业。
  3. 线程池收到Runnable后,从线程池一个线程启动Runnable,然后将该线程回收至空闲线程中。
  4. JobRunShell对象的run方法就是最终通过反射调用作业的地方。

企业应用实例:

第一步:首先配置文件,进行SchedulerFactoryBean初始化,进行Scheduler、trigger、job的创建。

@Configuration
@ConditionalOnProperty(name = "quartz.enable", havingValue = "true")
public class JobConfig{

    @Autowired
    private DataSource dataSource;
     
    //注册集群调度任务Scheduler
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
        SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
        scheduler.setDataSource(dataSource);     // 数据库
        scheduler.setJobFactory(jobFactory());   //作业实例和自动装配依赖项的作业工厂
        scheduler.setSchedulerName("Scheduler");
        //quartz.properties 文件配置
        scheduler.setQuartzProperties(quartzProperties("/quartz.properties"));  
        scheduler.setTriggers(requestEventExecuteTrigger.getObject());   //可以多个trigger
        return scheduler;
    }

    //创建Trigger
    @Bean(name = "requestEventExecuteTrigger")
    public CronTriggerFactoryBean requestEventExecuteTrigger() {
        CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
        factoryBean.setJobDetail(requestEventExecuteJob().getObject());
        factoryBean.setGroup(group);
        factoryBean.setName("requestEventExecuteTrigger");
        factoryBean.setCronExpression("0 0/5 * * * ? ");
        return factoryBean;
    }
 
    //创建JobDetail
    @Bean(name = "requestEventExecuteJob")
    public JobDetailFactoryBean requestEventExecuteJob() {
        JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
        factoryBean.setJobClass(RequestEventExecuteJob.class); //具体Job实现
        factoryBean.setGroup(group);
        factoryBean.setName("requestEventExecuteJob");
        factoryBean.setDurability(true);
        return factoryBean;
    }

    //创建作业实例和自动装配依赖项的作业工厂
    @Bean
    public AutowireableJobFactory jobFactory() {
        return new AutowireableJobFactory();
    }
}

 

第二步:配置quartz.properties文件

  1. 配置主调度器设置
  2. 配置ThreadPool
  3. 配置全局监听器
  4. 配置Scheduler调度程序插件
  5. 配置RMI                                      //远程服务器调用
  6. 配置RAMJobStore                     //内存存储机制实现类
  7. 配置JDBC-JobStoreTX             //受应用容器管理事物的数据库存储实现类
  8. 配置JDBC-JobStoreCMT          //不受应用容器事物管理的数据库存储实现类
  9. 配置DataSource
  10. 用JDBC-JobStore配置集群      
  11. 配置TerracottaJobStore

一般会写两种配置文件quartz.propertiesquartz-memory.properties,两个文件的唯一区别就是:

quartz.properties:任务存储的顶层接口org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
JobStoreTX使用数据库存储,TX是事物的意思,不管我们的Service服务本身业务代码是否执行成功,只要代码中调用了Quartz API的数据库操作,那任务状态就永久持久化了,就算业务代码抛出运行时异常任务状态也不会回滚到之前的状态。在这里就需要配置org.quartz.jobStore.dataSource属性。JobStoreCMT 也是使用数据库存储,不管是业务代码还是Quartz内部抛出异常,Service服务内的所有数据操作都会回滚到原始状态。JobStoreCMT和JobStoreTX最大的区别是JobStoreCMT需要配置两个数据源,一个是受应用容器管理的数据源,还有一个是不受应用容器管理的数据源。)

quartz-memory.properties:任务存储的顶层接口org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
(本地测试一般用这个,使用RAMJobStore当Quartz程序或应用程序停止了,伴随在内存中的数据也会被回收,任务等数据就永久丢失了,优点是任务的存储和读取的速度极快,使用内存存储时,注意配置文件中只需要保留基本的线程池配置和jobStore的实现类等几个简单的属性就行。如果使用了实现类中没有的属性,启动的时候会报错)

注解:

  1. 什么情况下使用RAMJobStore?有些任务是随着项目启动而启动的,就算项目关闭或系统宕机,那也没关系,因为项目重新启动后此任务又会随之启动。如果项目中只存在这类任务,那么就可以用内存存储。
  2. 什么情况下使用JobStoreTX?如果在一个Service服务中需要创建一个Job,那么请把创建Job的代码编写在服务代码的最后面,确保业务代码运行成功并且没有抛异常再去启动Job,如果启动Job失败的时候请抛出一个运行时异常使业务代码进行回滚。
  3. 什么情况下使用JobStoreCMT?看情况,一般很少用。

 第三步:创建作业实例和自动装配依赖项的作业工厂(jobFactory)

/**
 * The job factory for creating job instance and autowiring dependencies.
 */
public class AutowireableJobFactory extends AdaptableJobFactory{
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBean(Object)
     */

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobObject = super.createJobInstance(bundle);
        applicationContext.getAutowireCapableBeanFactory().autowireBean(jobObject);
        return jobObject;
    }
}

 

 第四步:使用Quartz有两种方法:extends QuartzJobBean和MethodInvokingJobDetailFactoryBean。这里主要介绍第一种方法。

public abstract class CronJob extends QuartzJobBean {
    private static Logger log = LoggerFactory.getLogger(CronJob.class);

    private static final String log4jLogNameKey = "jobName";

    @Override
    public void executeInternal(JobExecutionContext context) {
        String name = jobName();
        if (context != null) {
            name = context.getJobDetail().getKey().getName();
        }
        try {
            ThreadContext.put(log4jLogNameKey, name);
            log.info("{}:start", name);
            run(context);
            log.info("{}:end", name);
        } catch (Throwable throwable) {
            log.error("JOB {} execute error : {}", name,
                    StackTraceUtil.getStackTraceAsString(throwable));
        } finally {
            ThreadContext.remove(log4jLogNameKey);
        }
    }

    protected String jobName() {
        return getClass().getSimpleName();
    }


    public abstract void run(JobExecutionContext context) throws JobExecutionException;
}

 

第五步:具体Job实现service

@Slf4j
@DisallowConcurrentExecution
public class RequestEventExecuteJob extends CronJob{

    public void run(JobExecutionContext context) throws JobExecutionException {
        //service
    }
    
}

 

至于QuartzJob的线程是如何实现的,没有深入了解,暂时不写。以后再补。有写的不对的地方后续修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值