美文网首页我爱编程
使用quartz集群开发定时任务整理

使用quartz集群开发定时任务整理

作者: 天热蚊子多 | 来源:发表于2018-07-26 08:32 被阅读0次

           在微服务环境下,定时任务也需要独立为一个服务。这里使用spring+quartz搭建定时任务开发环境。

           在Config加载quartz.properties配置文件时,本地环境因为资源文件我们都存放在项目resource下,可使用ClassPathResource去拿到资源文件。可是在集成、测试、生产环境下,一般会把配置文件都拿出来统一放在项目外的一个文件中,而ClassPathResource会从项目根目录下开始查找资源,于是会拿不到项目外的quartz.properties,导致定时任务执行可能会与预期结果不一致,尤其是在集群环境中。读取资源文件可采用PathResouce读取配置文件的绝对路径。

           我们将调度信息存储在mysql中,按照quartz规范在数据库建立QRTZ_JOB_DETAILS,QRTZ_TRIGGERS等共11张表。建表sql可在quartz发型包中/docs/dbTables里看到。配置

    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 

    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

    org.quartz.jobStore.tablePrefix = QRTZ_ 

    quartz.properties有几个配置可以注意下。集群的配置

    org.quartz.jobStore.isClustered = true 开启集群特性

    org.quartz.jobStore.clusterCheckinInterval = 20000 设置Scheduler实例节点检测频率,节点出现问题会被发现

    org.quartz.jobStore.misfireThreshold = 60000 设置定时任务失火阈值,当前时间超过原定执行时间若是在阈值之内,就可以执行

           新建一个任务表用于存放我们配置要执行的任务信息quartz_config表,任务(组)名,触发器(组)名,执行类,cron表达式等,可以在前端页面对任务管理。在进行周期性任务状态变化检测时,需要取quartz_config内的值来进行判断。这里使用实现SchedulingConfigurer接口来完成动态定时任务

        /**

        * 执行定时任务.

        */

        @Override

        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

            taskRegistrar.addTriggerTask(

                    //1.添加任务内容(Runnable)

                    () -> quartzManager.chickJobs(),

                    //2.设置执行周期(Trigger)

                    triggerContext -> {

                        //2.1 从数据库获取执行周期

                        String cron = env.getProperty("job.cron");

                        //2.2 合法性校验.

                        if (StringUtils.isEmpty(cron)) {

                            cron="0/5 * * * * ?";

                        }

                        //2.3 返回执行周期(Date)

                        return new CronTrigger(cron).nextExecutionTime(triggerContext);

                    }

            );

        }

           如图,配置每分钟检测一次任务状态变化,检测功能在quartzManager.chickJobs()中实现。目前我们设置Job的状态有启动,暂停,删除,删除就逻辑删除,页面不展示,在quartz_config中以status字段来标示。我们点击启动任务后,做的操作就将status置为1,此时虽然显示已启动,可实际上是还未注册实例的,然后等待下一次任务状态检测,取到status为1的任务数据,然后使用scheduler.checkExists()检测当前scheduler实例是否已经存在该job,如果已经存在,则获取当前job实例的Cron表达式,判断任务的触发时间是否有变化,若有,则更新触发器

                    // 触发器

                    TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

                    // 触发器名,触发器组

                    triggerBuilder.withIdentity(triggerName, triggerGroupName);

                    triggerBuilder.startNow();

                    // 触发器时间设定

                    triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing());

                    // 创建Trigger对象

                    trigger = (CronTrigger) triggerBuilder.build();

                    // 方式一 :修改一个任务的触发时间

                    sched.rescheduleJob(triggerKey, trigger);

    否则添加一个job实例

           动态检测时对暂停和删除的Job的处理逻辑是,先取出quartz_config中status不等于1的数据,然后判断scheduler中是否存在该Job,存在就证明该job还是已注册的状态,就将该job从调度器中移除。

              Scheduler sched = schedulerFactory.getScheduler();

                TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

                log.info("===============remove Job:{}===============",jobName);

                sched.pauseTrigger(triggerKey);// 停止触发器

                sched.unscheduleJob(triggerKey);// 移除触发器

                sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务

           在集群多节点时,动态检测管理job状态,还需要做进一步控制,之前就因为没做控制,在本地时,就一台服务器,一直运行无误,找原因找了许久。假设集群中的两台服务器同时执行了任务检测逻辑,此时有一个任务点击启动(status=1),正在等待检测逻辑开始运行添加进实例(即insert进JOB_DETAILS,TRIGGERS等表),两台服务器同时拿到了这条待添加的job,必定有一台服务器先将任务实例持久化到数据库,另一台服务器在执行sched.scheduleJob(jobDetail,trigger)时,执行到底层storeJob方法时,就会报出ObjectAlreadyExistsException异常

    if (existingJob) {

                    if (!replaceExisting) {

                        throw new ObjectAlreadyExistsException(newJob);

                    }

                    this.getDelegate().updateJobDetail(conn, newJob);

                } else {

                    this.getDelegate().insertJobDetail(conn, newJob);

                }

           我们的处理方法是,在quartz_config表中新增一个process_status字段,来标示当前任务处理状态,1为待处理,2为处理中,3为处理完成,保证同时只有一个节点能执行该添加修改操作。quartz_config数据初始化改状态为1,暂停或修改Cron,都会讲process_status置为1,因为它是发生变化待检测逻辑处理的。这样的话,如上例,两节点同时执行下来,先到的一个会将process_status更新为2(处理中)。

          UPDATE plms_quartz_config SET process_status = #{processStatus,jdbcType=VARCHAR} WHERE job_name = #{jobName,jdbcType=VARCHAR} AND process_status = '1'

    更新成功则返回result = 1,只有result = 1的时候才会执行接下来的添加修改操作。当前处理状态已经从1变成2了,因为where条件的限制,另一节点到此已经更新不到改状态了,所以返回result = 0,就不会再一次addJob或addTirgger,避免对象已存在异常。

           有些情况下,到了指定时间才触发某个任务的执行可能满足不了需求,我们需要能手动触发一个任务立即执行来完成有些特殊情况。立即触发一个任务,我判断了当前正在执行中的任务不能立即执行。在任务执行中,该任务真正触发时间到了,需要执行,会导致任务重复执行,job类上因加上@DisallowConcurrentExecution防止任务重复执行(集群都需要)。解决重复执行了,可是任务可能会因为misfire失火机制在空闲时间或者下个轮询周期补偿此次的错失执行。看业务需要,配置失火策略,此处防止只执行一次的任务多次执行,我选择了失火之后忽略该任务不做补偿执行,实现方法是在rescheduleJob或scheduleJob之前设置触发器时,如下:

    riggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing());

    此处手动触发立即执行任务

    //执行任务

                    JobDetail job = JobBuilder.newJob((Class<? extends Job>) Class.forName(performJobReqDTO.getJobClassName())).withIdentity(jobKey).storeDurably().build();

                    scheduler.addJob(job, true);

                    scheduler.triggerJob(jobKey);

                    log.info("job:{}任务已手动触发",JSON.toJSONString(jobKey));

           又遇到了个坑。此处立即执行任务triggerJob触发后,会在JOB_DETAILS,TRIGGERS,SIMPLE_TRIGGERS表存入数据,触发完后会删除TRIGGER的数据,如若此时该任务正好处于刚点击了启动但是还未注册的情况,或者点立即执行马上又点击启动,因为立即执行导致JobDetail已经有该job的数据,任务状态检测的时候就不会将该任务新注册进去,导致只有jobDetail,但缺失触发器,该任务就永远不会执行。处理方法为,若任务处理状态为非处理完成,在立即执行触发后,清除该任务CronTrigger,SimpleTrigger,Trigger,JobDetail,当动态检测执行时,就能正常注册任务触发器。

           又有个坑,立即执行触发(scheduler.triggerJob(jobKey))后删除那几张表,可能会导致job实例不执行,任务触发成功,但是实际任务没跑,怀疑是triggerJob(jobKey)方法内部执行,开启一个线程后还需要去查表拿数据,清楚太快导致没拿到数据,就没跑成功,具体原因没仔细研究,我在此处的解决方法是删除之前让线程睡一秒,确保任务能正常执行。

           本次也是初次对集群轮询环境做这些大致的构建,做一下遇到的问题记录笔记。当然以上处理方式还有很多更好更严谨的处理方式有待优化。

    相关文章

      网友评论

        本文标题:使用quartz集群开发定时任务整理

        本文链接:https://www.haomeiwen.com/subject/khmmmftx.html