900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > Spring Boot 2.x 集成 Quartz 定时器 jdbc 持久化 配置集群

Spring Boot 2.x 集成 Quartz 定时器 jdbc 持久化 配置集群

时间:2021-05-08 22:58:27

相关推荐

Spring Boot 2.x 集成 Quartz 定时器 jdbc 持久化 配置集群

目录

JDBC JobStore 持久化步骤概述

Spring Boot 集成 Quartz 定时器

Scheduer 调度器常用方法

JobDetal 与 Trigger 一对多

Quartz Scheduler 配置集群

1、本文环境:Spring boot 2.1.3 + quartz 2.3.0 + Mysql 驱动 8.0.15 + H2 驱动 1.4 + Java JDK 1.8。

JDBC JobStore 持久化步骤概述

1、《Quartz 数据持久化》默认使用 org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore 内存存储,本文介绍 JobStoreTX jdbc 存储。

2、Quartz 调度信息可以通过 JDBC 保存到数据库中,支持主流关系型数据库,如:Oracle,PostgreSQL,MySQL,H2,SQL Server,HSQLDB 和 DB2 等。

3、实际生产中通常都是使用 JDBC 持久化,配置的调度信息都存储在数据中,应用服务器重启之后自动读取调度信息,然后继续按着规则进行自动执行,就像是一块钟表即使没电了,下次上了电池之后,仍然会自动运行。

一:创建数据库表

1、要使用 JDBC 持久化数据,首先必须创建一组数据库表以供 Quartz 使用,针对不同的数据库,org.quartz.impl.jdbcjobstore 包下提供了不同建表脚本。所有的表前缀都是 "QRTZ_",如 "QRTZ_TRIGGERS"、"QRTZ_JOB_DETAIL"。

2、执行程序之前,必须先手动执行脚本建表,否则启动时会报表不存在。而对于 h2 这种内存数据库,如果没有手动建表,则它会自动建表。

3、建表脚本传送,总共11张表,分别如下:

二:确定事务管理类型

1、JobStoreTX:如果不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用 JobStoreTX 管理事务(这是最常见的选择)。

2、JobStoreCMT:如果需要 Quartz 与其他事务(即J2EE应用程序服务器)一起工作,那么应该使用 JobStoreCMT,这种情况下,Quartz 将让应用程序服务器容器管理事务。

三:设置数据源

1、quartz 的 JDBC 持久化需要从 DataSource 中获取与数据库的连接,DataSources 可以通过三种方式进行配置:

四:确定数据库驱动代理

1、需要为 JobStore 选择一个 DriverDelegate 才能使用,驱动代理负责执行特定数据库可能需要的任何 JDBC 工作。

2、针对不同的数据库制作了不同的数据库的代理,其中使用最多的是 StdJDBCDelegate ,它是一个使用 JDBC 代码(和SQL语句)来执行其工作的委托。其他驱动代理可以在 "org.quartz.impl.jdbcjobstore" 包或其子包中找到。如 DB2v6Delegate(用于DB2版本6及更早版本),HSQLDBDelegate(用于HSQLDB),MSSQLDelegate(SQLServer),PostgreSQLDelegate(用于PostgreSQL)),WeblogicDelegate(用于使用Weblogic创建的JDBC驱动程序)

3、配置文件中配置如下:

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

4、quartz.properties 可用属性的完整配置信息可以参考官网 Quartz Configuration,但也不是完全能用到那么多,下面将通过实际案例来进行一一使用。

Spring Boot 集成 Quartz 定时器

1、通过封装公共的调度器业务层和控制层,以后需要新加功能时,则只需要新增 Job 实现即可,实现功能如下:

2、/wangmaoxiong/quartzjdbc 源码中都有详细注释,所以为了不重复赘述,下面只提醒注意事项。

一:数据库建表

1、上面已经提到:执行程序之前,必须先手动执行脚本建表,否则启动时会报错表不存在。而对于 h2 这种内存数据库,如果没有手动建表,则它会自动建表。

2、建表脚本。表的含义查看上面。

二:pom.xml 依赖:/wangmaoxiong/quartzjdbc/blob/master/pom.xml

1、导入了 mysql 数据库驱动和 h2 数据库驱动,如果没有安装 mysql 的,可以在配置文件中切换嵌入式的 h2 数据库.

2、spring-boot-starter-quartz 组件内部依赖了如下的组件:

三:全局配置文件:/wangmaoxiong/quartzjdbc/blob/master/src/main/resources/application.yml

1、如果没有安装 mysql 的,可以切换嵌入式的 h2 数据库:spring.profiles.active=h2DB

2、Spring Boot 对 Quartz Scheduler 集成之后,对它提供了属性配置,选项如下:

spring.quartz.auto-startup=true # 初始化后是否自动启动计划程序spring.ment-prefix=-- # SQL 初始化脚本中单行注释的前缀spring.quartz.jdbc.initialize-schema=embedded # 数据库架构初始化模式# 用于初始化数据库架构的SQL文件的路径spring.quartz.jdbc.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql spring.quartz.job-store-type=memory # 石英调度器作业/任务存储类型spring.quartz.overwrite-existing-jobs=false # 配置的作业是否应覆盖现有的作业定义spring.quartz.properties.*= # 其他石英调度器属性,值是一个 Mapspring.quartz.scheduler-name=quartzScheduler # 计划程序的名称spring.quartz.startup-delay=0s # 初始化完成后启动计划程序的延迟时间spring.quartz.wait-for-jobs-to-complete-on-shutdown=false # 关闭时是否等待正在运行的作业完成

Spring boot 2.1.6 文档官网:common-application-properties 中可以查看到这些配置信息.

3、org.springframework.boot.autoconfigure.quartz.QuartzProperties 类专门映射 spring.quartz 开头的属性.

4、quartz 调度器自己的配置属性既可以配置在自己的 quartz.properties 配置文件中,也可以直接配置在 Spring boot 的 spring.quartz.properties 属性下,它是一个 Map<String, String> 类型。

5、quartz 的所有配置都可以从官网 Quartz Configuration 获取.

四:BeanConfig 配置类:config/BeanConfig.java

1、Spring boot 源码 org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration 中可以看到应用启动时已经自动创建了 SchedulerFactoryBean 实例,所以需要注入它,然后 schedulerFactoryBean.getScheduler() 获取 Schduler。:

@Bean@ConditionalOnMissingBeanpublic SchedulerFactoryBean quartzScheduler() {SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();...

2、为了后期获取 Schduler,这里提供配置类将它提交给容器管理。同时提供了 RestTemplate 模板,方便后期做 http 请求.

五:页面返回值实体

1、ResultData 封装返回给页面的数据,由 code、message、data 三个属性组成,ResultCode 枚举定义常见的返回值状态.

六:调度信息实体:pojo/SchedulerEntity.java

1、为了方便注册调度任务与触发器,于是从 qrtz_job_details、qrtz_triggers 表中提取了一些常用字段出来组成实体,方便面向对象编程。

2、一是为了编码简单、二是考虑到 cron 触发器基本能满足开发需求,所以只操作 cron 触发器,其它触发器开发同理。

七:quartz 作业/任务:jobs/RequestJob.java

1、执行定时任务逻辑的类。为了方便注入其它 service,或者其它组件,所以将类标识为 @Service 组件。

2、《Job/JobDetail 实例 与 并发》中说过 @DisallowConcurrentExecution、@PersistJobDataAfterExecution 注解用于处理高并发情况,当同一个 JobDetail 实例被并发执行时,由于竞争,JobDataMap 中存储的数据很可能是不确定的。

3、项目中以后新增任务功能时,只需要再实现 Job 接口编写业务逻辑即可。

八:调度业务层:service/SchedulerService.java

1、专门封装了 "启动、暂停、恢复、删除作业(Job)或者触发器",封装之后就通用了,所有的任务注册、暂停、删除等操作,都可以传入参数调用本类。以后如果在需要添加业务需求时,则只需要关心实现 Job 即可。

2、因为事先已经提供了 Scheduler ,所以现在只需要使用 @Resource 注入即可。

3、注册作业的时候注意下面两个参数:

4、因为全局配置文件中配置了 spring.quartz.uto-startup=true,所以代码中不需要再手动启动:scheduler.start();

九:调度控制层:controller/SchedulerController.java

1、对外提供访问接口,通过约定参数进行注册任务、暂停、删除等操作。

2、http://localhost:8080/schedule/scheduleJob:注册作业并启动,post 提交的参数举例如下:

{"job_name": "j2000","job_group": "reqJobGroup","job_class_name": "com.wmx.quartzjdbc.jobs.RequestJob","job_data": {"url": "https://wangmaoxiong./article/details/105080021"},"trigger_name": "t2000","trigger_group": "requestGroup","trigger_desc": "每1分钟访问一次","cron_expression": "0 0/1 * * * ?"}

3、http://localhost:8080/schedule/rescheduleJob:重新注册任务的触发器,post 提交的参数举例如下:

{"trigger_name": "t2000","trigger_group": "requestGroup","trigger_desc": "每1分钟访问一次","cron_expression": "0 0/1 * * * ?","trigger_data": {"url": "https://wangmaoxiong./article/details/105057405"}}

其它接口都亲测有效,可以自行测试。

Scheduer 调度器常用方法

1、综上所述,整个调度器关键的就是 Scheduler 对象,所以它的常用 API 方法汇总如下:

JobDetal 与 Trigger 一对多

1、quartz 设计的 Job、Trigger、Calendar 是相互独立的。

2、Job 与 trigger 是一对多:qrtz_job_details 表中有外键关联 qrtz_job_details 表中的 sched_name, job_name, job_group。

3、Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,一个 Job 可以有多个 Trigger;这种松耦合的一个好处是可以修改或者替换 Trigger,而不用重新定义与之关联的 Job。

4、Calendar 通过 addCalendar 方法注册到 scheduler,触发器再通过 modifiedByCalendar(String calendarName)关联日历,同一个 Calendar 实例可用于多个 trigger。

5、主要是下面三个方法:

/*** 注册 job 与 触发器。区别于上面的是这里会对 作业和触发器进行分开注册.* job_class_name 不能为空时,注册 JobDetail 作业详情,如果已经存在,则更新.* cron_expression 不为空时,注册触发器(注册触发器时,对应的作业必须先存在):* <span>根据参数 job_name、job_group 获取 JobDetail,如果存在,则关联此触发器与 JobDetail,然后注册触发器,</span>** @param schedulerEntity* @return*/@PostMapping("schedule/scheduleJobOrTrigger")public ResultData scheduleJobOrTrigger(@RequestBody SchedulerEntity schedulerEntity) {ResultData resultData = null;try {schedulerService.scheduleJobOrTrigger(schedulerEntity);resultData = new ResultData(ResultCode.SUCCESS, null);} catch (Exception e) {resultData = new ResultData(ResultCode.FAIL, null);logger.error(e.getMessage(), e);}return resultData;}

业务层实现如下:

/*** 注册 job 与 触发器。区别于上面的是这里会对 作业和触发器进行分开注册.* job_class_name 不能为空时,注册 JobDetail 作业详情,如果已经存在,则更新,不存在,则添加.* cron_expression 不为空时,注册触发器(注册触发器时,对应的作业必须先存在):* <span>根据参数 job_name、job_group 获取 JobDetail,如果存在,则关联此触发器与 JobDetail,然后注册触发器,</span>** @param schedulerEntity* @throws SchedulerException*/public void scheduleJobOrTrigger(SchedulerEntity schedulerEntity) throws SchedulerException, ClassNotFoundException {//1)注册 job 作业String job_class_name = schedulerEntity.getJob_class_name();JobDetail jobDetail = null;if (StringUtils.isNotBlank(job_class_name)) {jobDetail = this.getJobDetail(schedulerEntity);//往调度器中添加作业.scheduler.addJob(jobDetail, true);logger.info("往调度器中添加作业 {}," + jobDetail.getKey());}//2)注册触发器,触发器必须关联已经存在的作业String job_name = schedulerEntity.getJob_name();String job_group = schedulerEntity.getJob_group();if (jobDetail == null && StringUtils.isNotBlank(job_group) && StringUtils.isNotBlank(job_name)) {jobDetail = scheduler.getJobDetail(JobKey.jobKey(job_name, job_group));}String cron_expression = schedulerEntity.getCron_expression();Trigger trigger = null;if (jobDetail != null && StringUtils.isNotBlank(cron_expression)) {trigger = this.getTrigger(schedulerEntity, JobKey.jobKey(job_name, job_group));}if (trigger == null) {return;}//注册触发器。如果触发器不存在,则新增,否则修改boolean checkExists = scheduler.checkExists(trigger.getKey());if (checkExists) {//rescheduleJob(TriggerKey triggerKey, Trigger newTrigger):更新指定的触发器.scheduler.rescheduleJob(trigger.getKey(), trigger);} else {//scheduleJob(Trigger trigger):注册触发器,如果触发器已经存在,则报错.scheduler.scheduleJob(trigger);}}/*** 内部方法:处理 Trigger* @param schedulerEntity* @return*/private Trigger getTrigger(SchedulerEntity schedulerEntity, JobKey jobKey) {//触发器参数//schedulerEntity 中 job_data 属性值必须设置为 json 字符串格式,所以这里转为 JobDataMap 对象.JobDataMap triggerDataMap = new JobDataMap();Map<String, Object> triggerData = schedulerEntity.getTrigger_data();if (triggerData != null && triggerData.size() > 0) {triggerDataMap.putAll(triggerData);}//如果触发器名称为空,则使用 UUID 随机生成. group 为null时,会默认为 default.if (StringUtils.isBlank(schedulerEntity.getTrigger_name())) {schedulerEntity.setTrigger_name(UUID.randomUUID().toString());}//过期执行策略采用:MISFIRE_INSTRUCTION_DO_NOTHING//forJob:为触发器关联作业. 一个触发器只能关联一个作业.TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();triggerBuilder.withIdentity(schedulerEntity.getTrigger_name(), schedulerEntity.getTrigger_group());triggerBuilder.withDescription(schedulerEntity.getTrigger_desc());triggerBuilder.usingJobData(triggerDataMap);if (jobKey != null && jobKey.getName() != null) {triggerBuilder.forJob(jobKey);}triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(schedulerEntity.getCron_expression()).withMisfireHandlingInstructionDoNothing());return triggerBuilder.build();}/*** 内部方法:处理 JobDetail* storeDurably(boolean jobDurability):指示 job 是否是持久性的。如果 job 是非持久的,当没有活跃的 trigger 与之关联时,就会被自动地从 scheduler 中删除。即非持久的 job 的生命期是由 trigger 的存在与否决定的.* requestRecovery(boolean jobShouldRecover) :指示 job 遇到故障重启后,是否是可恢复的。如果 job 是可恢复的,在其执行的时候,如果 scheduler 发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当 scheduler 重启时,该 job 会被重新执行。** @param schedulerEntity* @return* @throws ClassNotFoundException*/private JobDetail getJobDetail(SchedulerEntity schedulerEntity) throws ClassNotFoundException {//如果任务名称为空,则使用 UUID 随机生成.if (StringUtils.isBlank(schedulerEntity.getJob_name())) {schedulerEntity.setJob_name(UUID.randomUUID().toString());}Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(schedulerEntity.getJob_class_name());//作业参数JobDataMap jobDataMap = new JobDataMap();Map<String, Object> jobData = schedulerEntity.getJob_data();if (jobData != null && jobData.size() > 0) {jobDataMap.putAll(jobData);}//设置任务详情.return JobBuilder.newJob(jobClass).withIdentity(schedulerEntity.getJob_name(), schedulerEntity.getJob_group()).withDescription(schedulerEntity.getJob_desc()).usingJobData(jobDataMap).storeDurably(true).requestRecovery(true).build();}

src/main/java/com/wmx/quartzjdbc/controller/SchedulerController.java

src/main/java/com/wmx/quartzjdbc/service/SchedulerService.java

Quartz Scheduler 配置集群

1、现在的应用通常都采用多实例部署,使用集群的方式减轻服务器压力,防止单台服务器宕机而导致服务不可用。Quartz 的集群功能通过故障转移和负载均衡功能为调度程序带来高可用性和可扩展性。

2、quartz 集群适用JDBC 持久化(JobStoreTX 或 JobStoreCMT),并且基本上都是通过集群的每个节点共享相同的数据库来工作,即大家访问同一个数据。

3、quartz 集群有自己的负载均衡策略,每个节点都尽可能快地触发作业,当 Triggers 的触发时间发生时,获取到它的第一个节点(通过在其上放置一锁定)是将触发它的节点。

4、当某个节点出现故障时,其他节点会检测到该状况并识别数据库中在故障节点内正在进行的作业,任何标记为恢复的作业(requestRecovery=true)将被剩余的节点重新执行。没有标记为恢复的作业将在下一次相关的 Triggers 触发时执行。

5、集群功能最适合扩展长时间运行、或 cpu 密集型作业,通过多个节点分配工作负载,减轻服务器压力。

6、配置集群只需在非集群的基础上加上如下两项配置即可:

7、官方建议集群下的各个实例应该使用相同的 quartz.properties 文件,也就是使用相同的 quartz 配置,instanceId 配置项除外。quartz 官网集群配置

8、下面测试 quartz 集群,使用 IDEA 将同一个应用并行启动,即启动一次后,修改服务器端口,然后再继续启动:

测试结果显示:2个 quartz 实例同时启动,当触发任务时,只会有一个实例执行作业,其它实例不会执行;当其中一个宕机时,另一个会继续执行调度。

十:后记:

1、至此文章结束,github 源码中都有详细注释。作为后台开发,专注于提供规范的接口即可,所以示例中并未提供操作页面。

2、通过封装调度控制层与业务层之后,后续新增任务执行逻辑时,则只需要添加 Job 实现类即可,然后通过接口注册启动。

3、因为 qurtz 自身已经提供了 11 张表,所以本文直接提取了表中的字段封装为实体对象,没有再新建自己的表,生产中根据实际情况进行设计。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。