Quartz定时任务串行调度 fixedDelay

项目需要搞分布式,出于一些原因定时器的代码也需要部署两份,但是定时器是不需要跑两遍,所以考虑了分布式的定时任务框架Quartz。主要解决2个问题:

  1. 多台服务运行,保证只有一台服务的定时器在跑。这台服务不挂,另一台上的定时器永远不启动。
  2. 保证定时器串行调度。一个定时任务没有执行完,绝对不会触发第二次。(类似于Spring的定时器的fixedDelayString参数)

于是我就开始写demo,第一个很容易就得到了解决这里就不细说了,坑在了第二个,由于我用的是比较新的版本所以找了很多资料都不合适,结果没办法,跑去调试源码。(容易吗我,写个demo要去调源码)

网上很多人都说只要concurrent 设置为false

    @Bean("testJobDetail")
    public JobDetailFactoryBean testJobDetail() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setDurability(true);
        bean.setRequestsRecovery(true);
        bean.setJobClass(MyDetailQuartzJobBean.class);
        Map<String, String> map = new HashMap<>();
        //配置定时任务类
        map.put("targetObject", "testScheduleTask");
        map.put("targetMethod", "execute");
        //是否允许任务并发执行。当值为false时,表示必须等到前一个线程处理完毕后才再启一个新的线程
//        map.put("concurrent", "false");
        bean.setJobDataAsMap(map);
        return bean;
    }

或者,使QuartzJobBean类实现org.quartz.StatefulJob接口即可

public class BackCoupon implements StatefulJob {
    @Override
    public void execute(JobExecutionContext context)
              throws JobExecutionException {
    }
}

我都试过了完全没效果StatefulJob接口已经被废弃了不推荐使用,没办法了,撸起袖子调试源码吧。我们知道quartz是可以把jobdetail存到数据表里的


图片.png

我发现了一个很可疑的字段IS_NONCONCURRENT,入口点就在这里了。段点打在类的org.quartz.impl.jdbcjobstore.JobStoreSupport类storeJob方法,第610行,这里触发了jobdetail的一个更新操作,点进去发现了orcale的具体实现

 public int updateJobDetail(Connection conn, JobDetail job) throws IOException, SQLException {
        ByteArrayOutputStream baos = this.serializeJobData(job.getJobDataMap());
        byte[] data = baos.toByteArray();
        PreparedStatement ps = null;
        PreparedStatement ps2 = null;
        ResultSet rs = null;

        int var13;
        try {
            ps = conn.prepareStatement(this.rtp("UPDATE {0}JOB_DETAILS SET DESCRIPTION = ?, JOB_CLASS_NAME = ?, IS_DURABLE = ?, IS_NONCONCURRENT = ?, IS_UPDATE_DATA = ?, REQUESTS_RECOVERY = ?, JOB_DATA = EMPTY_BLOB()  WHERE SCHED_NAME = {1} AND JOB_NAME = ? AND JOB_GROUP = ?"));
            ps.setString(1, job.getDescription());
            ps.setString(2, job.getJobClass().getName());
            this.setBoolean(ps, 3, job.isDurable());
            this.setBoolean(ps, 4, job.isConcurrentExectionDisallowed());
            this.setBoolean(ps, 5, job.isPersistJobDataAfterExecution());
            this.setBoolean(ps, 6, job.requestsRecovery());
            ps.setString(7, job.getKey().getName());
            ps.setString(8, job.getKey().getGroup());
            ps.executeUpdate();
            ps.close();
            ps = conn.prepareStatement(this.rtp("SELECT JOB_DATA FROM {0}JOB_DETAILS WHERE SCHED_NAME = {1} AND JOB_NAME = ? AND JOB_GROUP = ? FOR UPDATE"));
            ps.setString(1, job.getKey().getName());
            ps.setString(2, job.getKey().getGroup());
            rs = ps.executeQuery();
            int res = 0;
            if (rs.next()) {
                Blob dbBlob = this.writeDataToBlob(rs, 1, data);
                ps2 = conn.prepareStatement(this.rtp("UPDATE {0}JOB_DETAILS SET JOB_DATA = ?  WHERE SCHED_NAME = {1} AND JOB_NAME = ? AND JOB_GROUP = ?"));
                ps2.setBlob(1, dbBlob);
                ps2.setString(2, job.getKey().getName());
                ps2.setString(3, job.getKey().getGroup());
                res = ps2.executeUpdate();
            }

            var13 = res;
        } finally {
            closeResultSet(rs);
            closeStatement(ps);
            closeStatement(ps2);
        }

        return var13;
    }

这个时候发现IS_NONCONCURRENT 字段是通过JOB的isConcurrentExectionDisallowed属性确定的。而且下方有个循环,这个循环就是把之前JobDetailFactoryBean 配置的map里的值取出来。看一下SQL,根本就不能配置IS_NONCONCURRENT 嘛,那么IS_NONCONCURRENT 怎么办呢?
点进去JobDetail的实现类查看相关的getter and setter发现如下东西:

  public boolean isConcurrentExectionDisallowed() {
        return ClassUtils.isAnnotationPresent(this.jobClass, DisallowConcurrentExecution.class);
    }

是否有出现标签?哦,原来是通过扫描标签啊,DisallowConcurrentExecution这个标签是个空标签。找到定义的job,在类名上加上标签。问题就得到了解决。

比如我这里绑定的job是MyDetailQuartzJobBean方法,用的是网上给的绑定方法map里面定义targetObject,targetMethod。然后通过反射调用自己写的定时任务,那么就在类名上加上注解即可。

package com.quartz.demo.service;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.lang.reflect.Method;


@DisallowConcurrentExecution
public class MyDetailQuartzJobBean extends QuartzJobBean {
    // 计划任务所在类
    private String targetObject;

    // 具体需要执行的计划任务
    private String targetMethod;
    private ApplicationContext ctx;

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        try {
            Object otargetObject = ctx.getBean(targetObject);
            Method m = null;
            try {
                m = otargetObject.getClass().getMethod(targetMethod);
                m.invoke(otargetObject);
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            throw new JobExecutionException(e);
        }
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.ctx = applicationContext;
    }

    public void setTargetObject(String targetObject) {
        this.targetObject = targetObject;
    }

    public void setTargetMethod(String targetMethod) {
        this.targetMethod = targetMethod;
    }
}

或者绑定JOB的时候就直接绑定到实际的定时任务类,不要通过反射(网友们说可能会出现序列化问题,我并没有尝试过)

环境:2.0.3的spring-boot-starter-quartz。orcale数据库。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.noraui</groupId>
            <artifactId>ojdbc7</artifactId>
            <version>12.1.0.2</version>
        </dependency>

        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.2.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

参考:

quartz任务串行并行

Scheduled with fixed delay in quartz scheduler?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容