Spring Quartz 指定Scheduler Name

Spring Quartz 指定Scheduler Name

May 31, 2020

当前项目使用quartz与spring来做基于数据库的集群化任务调度,但是在实际使用过程发现同一个scheduler中,不同group之间任务调度可能会相互跑串。当前项目中使用的 quartz-scheduler 版本 2.3.1, spring 版本 3.2.18.RELEASE。

举个例子,配置是这样的, 下面示例中隐去不重要的配置

<bean id="my-cluster-scheduler"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
          lazy-init="false">
    ...
    <property name="quartzProperties">
        <props>
            <prop key="org.quartz.jobStore.isClustered">true</prop>
            <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreCMT</prop>
        </props>
        ...
    </property>
    <property name="jobDetails">
        <list>
            <ref bean="myTestJobDetailBean"/>
        </list>
    </property>
    <property name="triggers">
        <list>
            <ref bean="myTestJobTrigger"/>
        </list>
    </property>
</bean>    

<bean id="myTestJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="myTestJobDetailBean"/>
    <property name="cronExpression" value="${my.test.job.cron.expression}"/>
    <property name="group" value="${{my.test.job.group}"/>
</bean>

<bean id="myTestJobDetailBean" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="net.zengxi.job.MyTestJob"/>
    <property name="durability" value="true"/>
    <property name="group" value="${my.test.job.group}"/>
    <property name="name" value="myTestJob"/>
</bean>

在配置文件中,通过${my.test.job.group}来指定group的名字。在部署的时候,部署了4台机器。机器1与2是一组,设置的值是group1;机器3与4是一组,设置的值是group2。按照正常情况下,两个分组不会相互干扰。调度任务的时候,group1的trigger,会从机器1与2里面选择一台机器来执行任务,group2的trigger会从3与4里面选择一台。但是,实际跑下来的结果是,偶尔会发现任务跑串了。比如,group2的trigger在调度任务的时候,居然从另外一个分组的机器1与2(期望的是3或者4)里面选择一台机器执行任务。这样在日志里面看到机器1与2把同一个任务执行了两遍,而机器3与4里面没有执行记录。

这个看上去是框架的bug。为了快速解决问题,想到了一个办法就是,将不同分组的调度器分开,也就是分别指定不同的scheduler。

通过quartz官方的文档,可以了解到,集群环境下的不同实例中如果scheduler name相同,则表示他们使用的是相同的scheduler。那我们就只需要让不同的分组使用不同的scheduler name

org.quartz.scheduler.instanceName

Can be any string, and the value has no meaning to the scheduler itself - but rather serves as a mechanism for client code to distinguish schedulers when multiple instances are used within the same program. If you are using the clustering features, you must use the same name for every instance in the cluster that is ‘logically’ the same Scheduler.

这里我们使用了spring框架,默认情况下,如果不指定scheduler name,使用的bean id指作为scheduler name。如果要指定自己的scheduler name,那么正常情况下,在spring的配置中设置 org.quartz.scheduler.instanceName 属性就可以了。然而,测试下来发现,这个配置没起作用。估计是spring的bug,至少在springboot中,其他人也发现过这个问题:

<bean id="my-cluster-scheduler"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
          lazy-init="false">
    ...
    <property name="quartzProperties">
        <props>
            <prop key="org.quartz.scheduler.instanceName">${my.test.job.group}-my-cluster-scheduler</prop>
            ...
        </props>
        ...
    </property>
    ...
</bean> 

考虑到升级第三方库可能给生产环境带来的风险,暂时不升级的话,只能从org.springframework.scheduling.quartz.SchedulerFactoryBean的源码中看看是否有快速的解决方案。后来发现它自带了schedulerName的getter与setter方法,这样就简单了,只需要配置schedulerName属性值即可。

<bean id="my-cluster-scheduler"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
          lazy-init="false">
    ...
    <property name="schedulerName" value="${my.test.job.group}-my-cluster-scheduler"/>
    <property name="quartzProperties">
        <props>
            ...
        </props>
        ...
    </property>
    ...
</bean> 
最后更新于