Spring Boot 2.4 集成定时任务 Quartz 单机模式以及集群模式

您所在的位置:网站首页 java单机模式 Spring Boot 2.4 集成定时任务 Quartz 单机模式以及集群模式

Spring Boot 2.4 集成定时任务 Quartz 单机模式以及集群模式

2024-07-17 02:48| 来源: 网络整理| 查看: 265

文章目录 1 摘要2 单机模式2.1 核心 Maven 依赖2.2 核心代码2.2.1 定时执行的业务代码2.2.2 定时任务负载类2.2.3 定时任务配置类2.2.4 SpringBoot 启动类 2.3 application.yml 配置文件2.4 运行日志 3 集群模式3.1 核心 Maven 依赖3.2 核心代码3.2.1 定时任务业务代码3.2.2 定时任务负载类3.2.3 定时任务配置类3.2.4 application.yml 配置文件3.2.5 数据源配置类3.2.6 SpringBoot 启动类3.2.7 手动创建定时任务 4 推荐参考资料5 Github 源码

​ ​

1 摘要

Quartz 作为经典的定时任务框架,有这广泛的应用,支持集群模式。本文将介绍基于 Spring Boot 2.4 集成 Quart 单机模式和集群模式。

Quart 官方文档: http://www.quartz-scheduler.org/documentation

Quartz 数据库表脚本文件: 下载官网压缩包,解压后在 ./quartz-2.4.0-SNAPSHOT/src/org/quartz/impl/jdbcjobstore 目录下找到对应的数据库脚本

2 单机模式 2.1 核心 Maven 依赖 ./demo-schedule-quartz/pom.xml org.springframework.boot spring-boot-starter-quartz ${springboot.version}

其中 ${springboot.version} 的版本为 2.4.0

2.2 核心代码 2.2.1 定时执行的业务代码 ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/service/UserService.java package com.ljq.demo.springboot.quartz.service; /** * @Description: 用户业务层接口 * @Author: junqiang.lu * @Date: 2020/11/14 */ public interface UserService { /** * 查询所有用户数量 * * @return */ int countAll(); } ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/service/impl/UserServiceImpl.java package com.ljq.demo.springboot.quartz.service.impl; import com.ljq.demo.springboot.quartz.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Random; /** * @Description: 用户业务层实现类 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Slf4j @Service("userService") @Transactional(rollbackFor = {Exception.class}) public class UserServiceImpl implements UserService { /** * 查询所有用户数量 * * @return */ @Override public int countAll() { int count = Math.abs(new Random().nextInt()); log.debug("用户总数为: {}", count); return count; } }

2.2.2 定时任务负载类 ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/job/UserJob.java package com.ljq.demo.springboot.quartz.job; import com.ljq.demo.springboot.quartz.service.UserService; import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.concurrent.atomic.AtomicInteger; /** * @Description: 用户模块工作负载 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Slf4j public class UserJob extends QuartzJobBean { @Autowired private UserService userService; private final AtomicInteger counts = new AtomicInteger(); @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.debug("【定时任务】第【{}】次执行,用户总数:{}", counts.incrementAndGet(), userService.countAll()); } } ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/job/UserJob2.java package com.ljq.demo.springboot.quartz.job; import lombok.extern.slf4j.Slf4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; /** * @Description: 用户工作负载2 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Slf4j public class UserJob2 extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.debug("------定时任务开始执行-------"); } }

2.2.3 定时任务配置类 ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/common/config/QuartzScheduleConfig.java package com.ljq.demo.springboot.quartz.common.config; import com.ljq.demo.springboot.quartz.job.UserJob; import com.ljq.demo.springboot.quartz.job.UserJob2; import org.quartz.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Description: Quartz 定时任务配置信息 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Configuration public class QuartzScheduleConfig { public static class UserJobConfig { /** * 工作负载名称 */ private static final String JOB_NAME = "userJob"; /** * 触发器名称 */ private static final String TRIGGER_NAME = "userJobTrigger"; @Bean public JobDetail userJob() { return JobBuilder.newJob(UserJob.class) .withIdentity(JOB_NAME) .storeDurably() .build(); } @Bean public Trigger userJobTrigger(){ SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(5) .repeatForever(); return TriggerBuilder.newTrigger() .forJob(JOB_NAME) .withIdentity(TRIGGER_NAME) .withSchedule(scheduleBuilder) .build(); } } public static class UserJob2Config { /** * 工作负载名称 */ private static final String JOB_NAME = "userJob2"; /** * cron 表达式 */ private static final String CRON_EXP = "0/10 * * * * ? *"; /** * 触发器名称 */ private static final String TRIGGER_NAME = "userJob2Trigger"; @Bean public JobDetail userJob2() { return JobBuilder.newJob(UserJob2.class) .withIdentity(JOB_NAME) .storeDurably() .build(); } @Bean public Trigger userJob2Trigger() { CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_EXP); return TriggerBuilder.newTrigger() .forJob(JOB_NAME) .withIdentity(TRIGGER_NAME) .withSchedule(scheduleBuilder) .build(); } } }

2.2.4 SpringBoot 启动类 ./demo-schedule-quartz/src/main/java/com/ljq/demo/springboot/quartz/DemoScheduleQuartzApplication.java package com.ljq.demo.springboot.quartz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author junqiang.lu */ @SpringBootApplication public class DemoScheduleQuartzApplication { public static void main(String[] args) { SpringApplication.run(DemoScheduleQuartzApplication.class, args); } } 2.3 application.yml 配置文件 ./demo-schedule-quartz/src/main/resources/application.yml ## spring config spring: quartz: scheduler-name: userSchedule job-store-type: memory auto-startup: true startup-delay: 1s wait-for-jobs-to-complete-on-shutdown: true overwrite-existing-jobs: false properties: org: quartz: threadPool: threadCount: 25 threadPriority: 5 class: org.quartz.simpl.SimpleThreadPool

配置信息参考官方文档: http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html

2.4 运行日志 2020-11-25 19:48:15 | DEBUG | userSchedule_Worker-1 | org.quartz.core.JobRunShell 201| Calling execute on job DEFAULT.userJob 2020-11-25 19:48:15 | DEBUG | userSchedule_Worker-1 | c.l.d.s.quartz.service.impl.UserServiceImpl 28| 用户总数为: 2034175457 2020-11-25 19:48:15 | DEBUG | userSchedule_Worker-1 | com.ljq.demo.springboot.quartz.job.UserJob 27| 【定时任务】第【1】次执行,用户总数:2034175457 2020-11-25 19:48:18 | DEBUG | userSchedule_Worker-2 | org.quartz.core.JobRunShell 201| Calling execute on job DEFAULT.userJob 2020-11-25 19:48:18 | DEBUG | userSchedule_QuartzSchedulerThread | org.quartz.core.QuartzSchedulerThread 291| batch acquisition of 1 triggers 2020-11-25 19:48:18 | DEBUG | userSchedule_Worker-2 | c.l.d.s.quartz.service.impl.UserServiceImpl 28| 用户总数为: 1515972786 2020-11-25 19:48:18 | DEBUG | userSchedule_Worker-2 | com.ljq.demo.springboot.quartz.job.UserJob 27| 【定时任务】第【1】次执行,用户总数:1515972786 2020-11-25 19:48:20 | DEBUG | userSchedule_QuartzSchedulerThread | org.quartz.core.QuartzSchedulerThread 291| batch acquisition of 1 triggers 2020-11-25 19:48:20 | DEBUG | userSchedule_Worker-3 | org.quartz.core.JobRunShell 201| Calling execute on job DEFAULT.userJob2 2020-11-25 19:48:20 | DEBUG | userSchedule_Worker-3 | com.ljq.demo.springboot.quartz.job.UserJob2 17| ------定时任务开始执行------- 2020-11-25 19:48:23 | DEBUG | userSchedule_QuartzSchedulerThread | org.quartz.core.QuartzSchedulerThread 291| batch acquisition of 1 triggers 2020-11-25 19:48:23 | DEBUG | userSchedule_Worker-4 | org.quartz.core.JobRunShell 201| Calling execute on job DEFAULT.userJob 2020-11-25 19:48:23 | DEBUG | userSchedule_Worker-4 | c.l.d.s.quartz.service.impl.UserServiceImpl 28| 用户总数为: 807472199 2020-11-25 19:48:23 | DEBUG | userSchedule_Worker-4 | com.ljq.demo.springboot.quartz.job.UserJob 27| 【定时任务】第【1】次执行,用户总数:807472199

3 集群模式 3.1 核心 Maven 依赖 ./demo-schedule-quartz-group/pom.xml org.springframework.boot spring-boot-starter-quartz ${springboot.version} mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-jdbc ${springboot.version}

其中 ${springboot.version} 的版本为 2.4.0

3.2 核心代码 3.2.1 定时任务业务代码

与单机模式一致

./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/service/UserService.java ./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/service/impl/UserServiceImpl.java

3.2.2 定时任务负载类

基本与单机模式一致,但是在类上添加了 @DisallowConcurrentExecution 注解,意为禁止并发运行,从而保证了在集群环境中,定时任务一次只有一台服务器在运行

./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/job/UserJob.java package com.ljq.demo.springboot.quartz.group.job; import com.ljq.demo.springboot.quartz.group.service.UserService; import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.QuartzJobBean; import java.util.concurrent.atomic.AtomicInteger; /** * @Description: 用户模块工作负载 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Slf4j @DisallowConcurrentExecution public class UserJob extends QuartzJobBean { @Autowired private UserService userService; private final AtomicInteger counts = new AtomicInteger(); @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.debug("【定时任务】第【{}】次执行,用户总数:{}", counts.incrementAndGet(), userService.countAll()); } } ./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/job/UserJob2.java package com.ljq.demo.springboot.quartz.group.job; import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; /** * @Description: 用户工作负载2 * @Author: junqiang.lu * @Date: 2020/11/14 */ @Slf4j @DisallowConcurrentExecution public class UserJob2 extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { log.debug("------定时任务开始执行-------"); } }

3.2.3 定时任务配置类

与单机模式一致

./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/common/config/QuartzScheduleConfig.java

3.2.4 application.yml 配置文件

这里配置两个数据源,是为了将 Quartz 数据源和业务的数据源区分开, Quartz 使用独立的数据源效率更高

./demo-schedule-quartz-group/src/main/resources/application.yml ## config ## server server: port: 8551 ## spring config spring: datasource: user: url: "jdbc:mysql://172.16.140.10:3306/demo?useUnicode=true&characterEncoding=utf8\ &useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8\ &useSSL=true&allowMultiQueries=true&autoReconnect=true&nullCatalogMeansCurrent=true\ &nullCatalogMeansCurrent=true" username: root password: "Qwert12345!" driver-class-name: com.mysql.cj.jdbc.Driver quartz: url: "jdbc:mysql://172.16.140.10:3306/schedule_quartz?useUnicode=true&characterEncoding=utf8\ &useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8\ &useSSL=true&allowMultiQueries=true&autoReconnect=true&nullCatalogMeansCurrent=true\ &nullCatalogMeansCurrent=true" username: root password: "Qwert12345!" driver-class-name: com.mysql.cj.jdbc.Driver quartz: scheduler-name: userSchedule job-store-type: jdbc auto-startup: true startup-delay: 1s wait-for-jobs-to-complete-on-shutdown: true overwrite-existing-jobs: false jdbc: initialize-schema: never properties: org: quartz: jobStore: dataSource: quartzDataSource class: org.quartz.impl.jdbcjobstore.JobStoreTX driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate tablePrefix: QRTZ_ isClustered: true clusterCheckinInterval: 1000 useProperties: false threadPool: threadCount: 25 threadPriority: 5 class: org.quartz.simpl.SimpleThreadPool

3.2.5 数据源配置类 ./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/common/config/DataSourceConfig.java package com.ljq.demo.springboot.quartz.group.common.config; import com.zaxxer.hikari.HikariDataSource; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.quartz.QuartzDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.util.StringUtils; import javax.sql.DataSource; /** * @Description: 数据源配置 * @Author: junqiang.lu * @Date: 2020/11/17 */ @Configuration public class DataSourceConfig { /** * 用户数据源配置(主数据源) * * @return */ @Primary @Bean("userDatasourceProperties") @ConfigurationProperties(prefix = "spring.datasource.user") public DataSourceProperties userDataSourceProperties() { return new DataSourceProperties(); } /** * 用户数据源(主数据源) * * @return */ @Primary @Bean("userDataSource") @ConfigurationProperties(prefix = "spring.datasource.user.hikari") public DataSource userDataSource() { DataSourceProperties properties = this.userDataSourceProperties(); return createHikariDataSource(properties); } /** * Quartz 数据源配置 * * @return */ @Bean("quartzDataSourceProperties") @ConfigurationProperties(prefix = "spring.datasource.quartz") public DataSourceProperties quartzDataSourceProperties() { return new DataSourceProperties(); } /** * Quartz 数据源 * * @return */ @Bean("quartzDataSource") @ConfigurationProperties(prefix = "spring.datasource.quartz.hikari") @QuartzDataSource public DataSource quartzDataSource() { DataSourceProperties properties = this.quartzDataSourceProperties(); return createHikariDataSource(properties); } /** * 创建 Hikari 数据库连接池 * * @param properties * @return */ private HikariDataSource createHikariDataSource(DataSourceProperties properties) { HikariDataSource dataSource = properties.initializeDataSourceBuilder() .type(HikariDataSource.class) .build(); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } }

@Primary 表名数据源为主数据源

@QuartzDataSource 指明该数据源为 Quartz 框架的数据源

3.2.6 SpringBoot 启动类

这里为了模拟集群工作环境,写两个 SpringBoot 启动类,端口号区分开

./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/DemoScheduleQuartzGroupApplication.java package com.ljq.demo.springboot.quartz.group; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author apple */ @SpringBootApplication public class DemoScheduleQuartzGroupApplication { public static void main(String[] args) { SpringApplication.run(DemoScheduleQuartzGroupApplication.class, args); } } ./demo-schedule-quartz-group/src/main/java/com/ljq/demo/springboot/quartz/group/DemoScheduleQuartzGroupApplication2.java package com.ljq.demo.springboot.quartz.group; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @Description: Quartz 定时任务集群模式应用启动类2 * @Author: junqiang.lu * @Date: 2020/11/18 */ @SpringBootApplication public class DemoScheduleQuartzGroupApplication2 { public static void main(String[] args) { System.setProperty("server.port", "8552"); SpringApplication.run(DemoScheduleQuartzGroupApplication2.class); } }

依次将启动这两个程序,即可实现集群工作的效果,观察两个控制台的输出日志,会发现两边都会有日志输出,但是在同一时间只有一个定时任务在运行

3.2.7 手动创建定时任务

直接启动 SpringBoot 项目,程序会自动创建定时任务,但定时任务也可以通过手动的方式创建,可以选择是否覆盖已有任务

测试类

./demo-schedule-quartz-group/src/test/java/com/ljq/demo/springboot/quartz/group/common/config/QuartzScheduleConfigTest.java package com.ljq.demo.springboot.quartz.group.common.config; import com.ljq.demo.springboot.quartz.group.DemoScheduleQuartzGroupApplication; import com.ljq.demo.springboot.quartz.group.job.UserJob; import com.ljq.demo.springboot.quartz.group.job.UserJob2; import org.junit.jupiter.api.Test; import org.mockito.internal.util.collections.Sets; import org.quartz.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; /** * 定时任务配置测试类 */ @SpringBootTest(classes = DemoScheduleQuartzGroupApplication.class) class QuartzScheduleConfigTest { @Autowired private Scheduler scheduler; private static final String USER_JOB_DETAIL_NAME = "userJob001"; private static final String USER_JOB_TRIGGER_NAME = "userJobTrigger001"; private static final String USER_JOB_2_DETAIL_NAME = "userJob002"; private static final String USER_JOB_2_TRIGGER_NAME = "userJob2Trigger002"; private static final String USER_JOB_2_CRON = "0/10 * * * * ? *"; /** * 手动添加用户定时任务配置 * * @throws SchedulerException */ @Test public void addUserJobConfig() throws SchedulerException { // 创建 JobDetail JobDetail jobDetail = JobBuilder.newJob(UserJob.class) .withIdentity(USER_JOB_DETAIL_NAME) .storeDurably() .build(); // 创建 Trigger SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(5) .repeatForever(); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(USER_JOB_TRIGGER_NAME) .withSchedule(scheduleBuilder) .build(); // 添加调度任务 // 不覆盖已有任务 // scheduler.scheduleJob(jobDetail, trigger); // 覆盖已有任务 scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true); } /** * 手动创建用户2定时任务 * * @throws SchedulerException */ @Test public void addUserJob2Config() throws SchedulerException { // 创建 JobDetail JobDetail jobDetail = JobBuilder.newJob(UserJob2.class) .withIdentity(USER_JOB_2_DETAIL_NAME) .storeDurably() .build(); // 创建 Trigger CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(USER_JOB_2_CRON); Trigger trigger = TriggerBuilder.newTrigger() .forJob(jobDetail) .withIdentity(USER_JOB_2_TRIGGER_NAME) .withSchedule(scheduleBuilder) .build(); // 添加调度任务 // 不覆盖已有任务 // scheduler.scheduleJob(jobDetail, trigger); // 覆盖已有任务 scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true); } }

无论是手动创建还是自动创建,效果是一样的

4 推荐参考资料

Quartz 官方: http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/index.html

Spring Job?Quartz?XXL-Job

5 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注. 404Code



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3