分布式应用部署模式下的Quartz配置

本来计划做一次应用的部署升级,由单机模式,改为集群模式。但是在考虑方案时,除了遇到的SpringBoot优雅退出问题,还有一个需要考虑的问题,就是Quartz定时任务的处理。

单机模式下,quartz定时任务很简单,按照文档使用即可,使用RAM模式保存数据,不需要数据库保存数据,也不涉及任务调度加锁问题。

但是在集群模式下,quartz就需要考虑开启cluster模式了,从而避免同一个任务,被多个服务器进行触发。

quartz开启cluster模式只需要进行简单的配置即可,但是必须启用JDBC持久化存储(也可以使用Terracotta,但是太小众)。

对于quartz的cluster如何配置,网上很多,不再赘述。本文主要说明一下遇到的几个问题。

1、如何使用springboot与quartz集成

在非springboot下,我们需要自行创建SchedulerFactoryBean,并且指定jobFactory等一些必要的配置。

但是在SpringBoot下,我们可以借助autoconfiguration,自动完成这些bean的创建,只需要在yml里面进行简单配置即可。

首先,引入依赖包:

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

通过QuartzAutoConfiguration类,我们可以看到schedulerFactoryBean对象的初始化过程。其中所支持的一些配置方式,都在QuartzProperties中有描述。

然后在yml(或者properties)里对quartz进行配置:

spring:
  # --------Quartz相关配置
  quartz:
    scheduler-name: XXXQuartzScheduler
    wait-for-jobs-to-complete-on-shutdown: true
    job-store-type: jdbc
    jdbc:
      initialize-schema: never
    properties:
      org:
        quartz:
          scheduler:
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
            #dataSource: xxxxDataSource
            useProperties: true
            tablePrefix: T_QRTZ_
            isClustered: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true

除了spring.quartz.propertis下的配置(会原样传递至quartz,与quartz的配置是一致的),其他配置都是springboot进行包装后的quartz相关的配置,与quartz的配置其实是对应的。

具体的参数说明,网上都有介绍,不再赘述。

2、如果按照上面的配置,启动工程会报错,提示未配置DataSource。

quartz默认的是自行管理JDBC connection pool,目前支持的包括C3P0(默认)、HarikiCP,当然也可以进行自定义,只需要实现org.quartz.utils.ConnectionProvider即可。

具体用法参考:How to Use DB Connection Pool · quartz-scheduler/quartz Wiki · GitHubCode for Quartz Scheduler. Contribute to quartz-scheduler/quartz development by creating an account on GitHub.https://github.com/quartz-scheduler/quartz/wiki/How-to-Use-DB-Connection-Pool#using-custom-db-conn-pool

对于我们应用中用到的Druid,目前Druid也自带了Quartz的支持,提供了DruidQuartzConnectionProvider来使用,具体可以参考:

quartz连druid报错。应该如何配置烦请大神指点 · Issue #2186 · alibaba/druid · GitHub[ERROR][2017-12-12 14:21:46.012][scheduler_QuartzSchedulerThread]:[An error occurred while scanning for the next triggers to fire.][org.quartz.core.ErrorLogger:2425] org.quartz.JobPersistenceException: Couldn't acquire next trigger: Erro...https://github.com/alibaba/druid/issues/2186

这种方式实现后,quartz单独管理着所需的数据库连接池。

(!!!需要注意:必须指定org.quartz.dataSource,且需要配置该名称的DataSource,具体配置方式参考:)quartz/docs/configuration.adoc at main · quartz-scheduler/quartz · GitHubCode for Quartz Scheduler. Contribute to quartz-scheduler/quartz development by creating an account on GitHub.https://github.com/quartz-scheduler/quartz/blob/master/docs/configuration.adoc#quartz-created-datasources-are-defined-with-the-following-properties但是,对于我们的应用来说,quartz并没有单独部署集群,而是集成在应用内的,quartz相关的数据库表也创建在应用本身的数据库中。所以并不希望再重新创建一个独立的数据库连接池,而是能够直接复用应用本身的连接池。

quartz似乎没有提供指定连接池DataSource对象的配置参数。org.quartz.dataSource指定的是通过quartz配置的DataSource的name。

查看了SchedulerFactoryBean类的源码,其实scheduler提供了setDataSource方法,用来指定DataSource对象。但是QuartzAutoConfiguration里面并没有提供配置DataSource的方式。

那解决方案有两种:

一是不再使用AutoConfiguration生成的schedulerFactoryBean,而是自行创建。然后设置所需的DataSource。但是这种方式就没法用springboot提供的自动装配功能了。

另一种方式就是借助SchedulerFactoryBeanCustomizer,这个接口是SpringBoot quartz autoconfiguration提供给的一个定制化配置schedulerFactoryBean的口子。

通过这个方式,可以完美解决指定DataSource对象的问题。

@Component
public class QuartzSchedulerFactoryBean implements SchedulerFactoryBeanCustomizer {
    @Resource
    private SimpleJobListener simpleJobListener;
    @Resource
    private DruidDataSource xxxDataSource;

    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        schedulerFactoryBean.setGlobalJobListeners(simpleJobListener);
        schedulerFactoryBean.setDataSource(xxxDataSource);
    }
}

说明:xxxDataSource就是在其他地方定义好的DataSource,因为都被spring托管,可以直接引用。

说明2:SimpleJobListener是自定义的一个joblistener (与此问题无关),通过指定joblistener,可以实现一些自定义的任务事件处理逻辑。示例代码如下:

@Component
public class SimpleJobListener extends JobListenerSupport {
    @Autowired
    private SystemJobLogService systemJobLogService;
    private static final Logger logger = LoggerFactory.getLogger(SimpleJobListener.class);

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        // 设置起始时间
        context.getMergedJobDataMap().put(JobConstant.KEY_JOB_START, new Date());
    }


    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        Short jobResult = JobConstant.JOB_RESULT_SUCCESS;
        String errorMsg = null;
        if (jobException != null) {
            jobResult = JobConstant.JOB_RESULT_ERROR;
            errorMsg = jobException.getMessage();
        }

        try {
            Long jobId = (Long) context.getMergedJobDataMap().get(JobConstant.KEY_JOB_ID);
            Date jobStart = (Date) context.getMergedJobDataMap().get(JobConstant.KEY_JOB_START);
            String userId = context.getMergedJobDataMap().getString(JobConstant.KEY_JOB_USER_ID);
            userId = userId == null ? JobConstant.JOB_USER_ID_DEFAULT : userId;
            SystemJobLog systemJobLog = new SystemJobLog();
            systemJobLog.setJobId(jobId);
            systemJobLog.setJobResult(jobResult);
            systemJobLog.setErrorMsg(errorMsg);
            systemJobLog.setJobStart(jobStart);
            systemJobLog.setJobEnd(new Date());
            systemJobLog.setUserId(userId);
            systemJobLogService.insert(systemJobLog);
        } catch (Exception ex) {
            logger.error("记录JOB日志错误:", ex);
        }
    }

    @Override
    public String getName() {
        return getClass().getSimpleName();
    }
}

参考资料:

springboot整合quartz(集群环境)_GreaterBuilder的博客-CSDN博客quartz_springboot整合quartzhttps://blog.csdn.net/qq_26709459/article/details/82726985

How to Use DB Connection Pool · quartz-scheduler/quartz Wiki · GitHubCode for Quartz Scheduler. Contribute to quartz-scheduler/quartz development by creating an account on GitHub.https://github.com/quartz-scheduler/quartz/wiki/How-to-Use-DB-Connection-Pool#using-custom-db-conn-pool

PS:Quartz启动时的日志很有误导性,看到running locally、currently in standby mode等,还以为集群模式没生效,但是其实只要最后面有 which supports persistence. and is clusetered。

一般就说明能够正常运行了,然后查询一下数据库的那几个相关表,里面有数据,基本上就没问题了。

 INFO  main  org.quartz.core.QuartzScheduler:294 - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'TaQuartzScheduler' with instanceId 'xxx-PC21647829349100'

  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.

  NOT STARTED.

  Currently in standby mode.

  Number of jobs executed: 0

  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.

  Using job-store 'org.springframework.scheduling.quartz.LocalDataSourceJobStore' - which supports persistence. and is clustered.

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Quartz是一种广泛使用的分布式定时作业调度框架,可用于在多台服务器上触发执行一组作业。以下是Quartz分布式定时任务框架的搭建步骤: 1. 安装数据库 Quartz框架需要一个持久性存储器来保存作业调度信息。选择一个数据库(如MySQL或Oracle)并创建Quartz的表结构。 2. 配置Quartz实例 在Quartz中,每个实例代表一个独立的调度器,可以在其上启动和停止作业调度。要配置Quartz实例,需要在代码中指定一些配置属性,例如数据库连接信息和调度器的名称。 3. 创建作业和触发器 在Quartz中,作业代表要执行的代码逻辑,而触发器则指定作业何时运行。可以使用Cron表达式来指定触发器的运行时间。要创建新的作业和触发器,请使用Quartz的API。 4. 创建分布式Quartz实例 为了使Quartz在分布式环境中运行,需要在所有服务器上创建Quartz实例。这些实例应该使用相同的数据库,并且应该使用相同的配置属性。 5. 配置集群 在分布式环境中,Quartz需要一种机制来协调调度器的运行。可以通过使用第三方插件(如Terracotta)或Quartz提供的基于数据库的集群实现来实现这一点。 6. 启动Quartz实例 一旦完成了配置集群设置,可以启动Quartz实例并开始调度作业。要启动Quartz实例,需要使用Quartz的API方法。 总之,搭建Quartz分布式定时任务框架需要一定的技术和经验。最好在熟悉Quartz框架之前不要尝试这个任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金融码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值