参考链接:
https://blog.csdn.net/wangmx1993328/article/details/105441308
https://blog.csdn.net/qq_39669058/article/details/90411497
参数配置连接:https://github.com/wangmaoxiong/quartzjdbc/blob/master/src/main/resources/application-cluster.yml
factory.setWaitForJobsToCompleteOnShutdown(true);
确认这个配置集群需要配置吗?
false
开源项目若依定时任务配置
package com.ruoyi.quartz.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.util.Properties;/*** 定时任务配置* * @author ruoyi*/
@Configuration
public class ScheduleConfig
{@Beanpublic SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource){SchedulerFactoryBean factory = new SchedulerFactoryBean();factory.setDataSource(dataSource);// quartz参数Properties prop = new Properties();//实例名称可以自定义prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");prop.put("org.quartz.scheduler.instanceId", "AUTO");// 线程池配置prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");prop.put("org.quartz.threadPool.threadCount", "20");prop.put("org.quartz.threadPool.threadPriority", "5");// JobStore配置prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");// 集群配置prop.put("org.quartz.jobStore.isClustered", "true");prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "false");// sqlserver 启用prop.put("org.quartz.jobStore.misfireThreshold", "12000");prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");factory.setQuartzProperties(prop);//实例名称可以自定义factory.setSchedulerName("RuoyiScheduler");// 延时启动factory.setStartupDelay(1);factory.setApplicationContextSchedulerContextKey("applicationContextKey");// 可选,QuartzScheduler// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了factory.setOverwriteExistingJobs(true);factory.setWaitForJobsToCompleteOnShutdown(true);// 设置自动启动,默认为truefactory.setAutoStartup(true);return factory;}
}
一句话概括Quartz:
Quart分布式调度任务是通过数据库实现的,抢占式调度,一个任务只能在一个节点上执行,他的集群也仅仅是解决了单点故障(任务级别),实现了高可用,多个任务在集群中负载均衡调度,并没有解决任务分片的问题,不能实现水平扩展,如果执行大量的短任务,各个节点频繁的竞争数据库锁,节点越多这种情况越严重,性能会很低下。
版本为:SpringBoot 2.x,Quartz 2.3.0,Durid 1.1.9
这里不再使用c3p0数据源,在boot2.x中spring-boot-starter-quartz依赖默认是不依赖c3p0数据源的,如果要使用需要自己单独引用c3p0数据源,在quartz.properties配置下就可以了,这里使用的Druid数据源。
首先引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.43</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.9</version></dependency>
Druid-spring-boot-starter:
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
application.yml文件配置,这里面配置的Druid数据源相关的,没有配置其他的,很单纯的
server:port: 8080
spring:application:name: quartzdatasource:url: jdbc:mysql://192.168.184.135:3306/quartzdb?characterEncoding=utf-8&useUnicode=true&useSSL=falsedriver-class-name: com.mysql.jdbc.Driver # mysql8.0以前使用com.mysql.jdbc.Driverusername: rootpassword: 123456platform: mysql#通过这句配置将druid连接池引入到我们的配置中,spring会尽可能判断类型是什么,然后根据情况去匹配驱动类。type: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5 # 初始化大小min-idle: 5 # 最小max-active: 100 # 最大max-wait: 60000 # 配置获取连接等待超时的时间time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒min-evictable-idle-time-millis: 300000 # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒validationQuery: select 'x'test-while-idle: true # 当连接空闲时,是否执行连接测试test-on-borrow: false # 当从连接池借用连接时,是否测试该连接test-on-return: false # 在连接归还到连接池时是否测试该连接filters: config,wall,stat # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小maxPoolPreparedStatementPerConnectionSize: 20maxOpenPreparedStatements: 20# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false# 合并多个DruidDataSource的监控数据#use-global-data-source-stat: true#WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilterweb-stat-filter:enabled: true #是否启用StatFilter默认值trueurl-pattern: /*exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.icosession-stat-enable: truesession-stat-max-count: 10#StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置stat-view-servlet:enabled: true #是否启用StatViewServlet默认值trueurl-pattern: /druid/*reset-enable: truelogin-username: adminlogin-password: admin
quartz.properties配置文件
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName = quartzScheduler
#调度器实例编号自动生成,每个实例不能不能相同
org.quartz.scheduler.instanceId = AUTO
#开启分布式部署,集群
org.quartz.jobStore.isClustered = true
#分布式节点有效性检查时间间隔,单位:毫秒,默认值是15000
org.quartz.jobStore.clusterCheckinInterval = 2000
#远程管理相关的配置,全部关闭
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
#实例化ThreadPool时,使用的线程类为SimpleThreadPool(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
##并发个数,指定线程数,至少为1(无默认值)(一般设置为1-100之间的的整数合适)
org.quartz.threadPool.threadCount = 10
##设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#容许的最大作业延长时间,最大能忍受的触发超时时间,如果超过则认为“失误”,不敢再内存中还是数据中都要配置
org.quartz.jobStore.misfireThreshold = 6000
#持久化方式配置
# 默认存储在内存中,保存job和Trigger的状态信息到内存中的类
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#数据库方式
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#持久化方式配置数据驱动,MySQL数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz相关数据表前缀名
org.quartz.jobStore.tablePrefix = QRTZ_#数据库别名 随便取
#org.quartz.jobStore.dataSource = qzDS
#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://192.168.184.135:3306/quartzdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8
#org.quartz.dataSource.qzDS.user = root
#org.quartz.dataSource.qzDS.password = 123456
#org.quartz.dataSource.qzDS.maxConnections = 10
#org.quartz.dataSource.qzDS.acquireIncrement=1
///分割一下///
我这里使用的是MySql数据库,想要使用Oracle数据库,导入表(@"/路径/tables_oracle.sql"),不管你怎么导,只要能用就行,在数据源的地方修改下连接信息,修改quartz.properties文件修改为如下,直接启动就行了
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
///分割一下///
案例结构
模拟任务调度service层的业务逻辑
UserService.java
package com.quartz.service;import java.io.Serializable;public interface UserService extends Serializable {void getUserInfo();void getUserAddr();
}
UserServiceImpl.java
package com.quartz.service.impl;import com.quartz.service.UserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {@Overridepublic void getUserInfo() {System.err.println("调度getUserInof成功");}@Overridepublic void getUserAddr() {System.err.println("调度getUserAddr成功");}
}
任务
JobOne.java
package com.quartz.job;import com.quartz.service.UserService;
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 org.springframework.stereotype.Component;@Component
@DisallowConcurrentExecution //保证上一次任务执行完毕再执行下一任务
//@PersistJobDataAfterExecution //上一个任务完成前写入需要被下一个任务获取的变量以及对应的属性值,类似求和累加
public class JobOne extends QuartzJobBean {private UserService userService;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {userService.getUserInfo();}public void setUserService(UserService userService) {this.userService = userService;}
}
JobTwo.java
package com.quartz.job;import com.quartz.service.UserService;
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 org.springframework.stereotype.Component;@Component
@DisallowConcurrentExecution
public class JobTwo extends QuartzJobBean {//不能使用注入的方式,只能使用DateMap方式传入参数private UserService userService;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {userService.getUserAddr();}public void setUserService(UserService userService) {this.userService = userService;}
}
JobConfig.java
package com.quartz.jobconfig;import com.quartz.job.JobOne;
import com.quartz.job.JobTwo;
import com.quartz.service.UserService;
import org.quartz.JobDataMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;@Configuration
public class JobConfig {@Autowiredprivate UserService userService;@Bean("jobOneDetail")public JobDetailFactoryBean jobOneDetailFactoryBean(JobOne jobOne) {JobDetailFactoryBean jobDetailFactoryBean =new JobDetailFactoryBean();jobDetailFactoryBean.setJobClass(jobOne.getClass());//没有绑定触发器仍然保留在Quartz的JobStore中jobDetailFactoryBean.setDurability(true);jobDetailFactoryBean.setName("jobOneDetailName");jobDetailFactoryBean.setGroup("jobOneDetailGroup");JobDataMap jobDataMap = new JobDataMap ();jobDataMap.put ("userService",userService);jobDetailFactoryBean.setJobDataMap(jobDataMap) ;return jobDetailFactoryBean;}@Bean("jobOneTrigger")public CronTriggerFactoryBean cronTriggerOneFactoryBean(@Qualifier("jobOneDetail") JobDetailFactoryBean jobDetailFactoryBean){CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());cronTriggerFactoryBean.setCronExpression("*/1 * * * * ?");cronTriggerFactoryBean.setName("jobOneTriggerName");cronTriggerFactoryBean.setGroup("jobOneTriggerGroup");return cronTriggerFactoryBean;}@Bean("jobTwoDetail")public JobDetailFactoryBean jobTwoDetailFactoryBean(JobTwo jobTwo) {JobDetailFactoryBean jobDetailFactoryBean =new JobDetailFactoryBean();jobDetailFactoryBean.setJobClass(jobTwo.getClass());jobDetailFactoryBean.setDurability(true);jobDetailFactoryBean.setName("jobTwoDetailName");jobDetailFactoryBean.setGroup("jobTwoDetailGroup");JobDataMap jobDataMap = new JobDataMap ();jobDataMap.put ("userService",userService);jobDetailFactoryBean.setJobDataMap(jobDataMap) ;return jobDetailFactoryBean;}@Bean("jobTwoTrigger")public CronTriggerFactoryBean cronTriggerTwoFactoryBean(@Qualifier("jobTwoDetail") JobDetailFactoryBean jobDetailFactoryBean){CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());cronTriggerFactoryBean.setCronExpression("*/1 * * * * ?");cronTriggerFactoryBean.setName("jobTwoTriggerName");cronTriggerFactoryBean.setGroup("jobTwoTriggerGroup");return cronTriggerFactoryBean;}}
SchedulerConfig.java
package com.quartz.jobconfig;import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;@Configuration
public class SchedulerConfig {//执行任务。有了触发器,我们就可以执行任务了。注册一个SchedulerFactroyBean,然后将触发器一list的方式传入@Beanpublic SchedulerFactoryBean schedulerFactoryBean(DruidDataSource druidDataSource, @Qualifier("jobOneTrigger") Trigger jobOneTrigger, @Qualifier("jobTwoTrigger") Trigger jobTwoTrigger){SchedulerFactoryBean schedulerFactoryBean=new SchedulerFactoryBean();//调度器名称schedulerFactoryBean.setSchedulerName("TestScheduler");//数据源schedulerFactoryBean.setDataSource(druidDataSource);//覆盖已存在的任务,用于Quartz集群,QuartzScheduler启动会更新已存在的JobschedulerFactoryBean.setOverwriteExistingJobs(true);//延时1s启动定时任务,避免系统未完全启动却开始执行定时任务的情况schedulerFactoryBean.setStartupDelay(1);//设置加载的quartz.properties配置文件schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));//自动启动schedulerFactoryBean.setAutoStartup(true);//注册触发器schedulerFactoryBean.setTriggers(jobOneTrigger,jobTwoTrigger);return schedulerFactoryBean;}
}
启动两个改下端口8080,8081就行了,看到效负载调度。
节点争抢Job问题
因为Quartz使用了一个随机的负载均衡算法, Job以随机的方式由不同的实例执行。Quartz官网上提到当前,还不存在一个方法来指派(钉住) 一个 Job 到集群中特定的节点。
参考链接:https://blog.csdn.net/qq_39669058/article/details/90411497